* Create a folder on disk and register it in ACL with the creator as owner. * @param string $folderName leaf name * @param string $parent 'root' or nested key (e.g. 'team/reports') * @param string $creator username to set as initial owner (falls back to 'admin') */
(string $folderName, string $parent, string $creator)
| 1524 | * @param string $creator username to set as initial owner (falls back to 'admin') |
| 1525 | */ |
| 1526 | public static function createFolder(string $folderName, string $parent, string $creator): array |
| 1527 | { |
| 1528 | // -------- Normalize incoming values (use ONLY the parameters) -------- |
| 1529 | $folderName = trim((string)$folderName); |
| 1530 | $parentIn = trim((string)$parent); |
| 1531 | |
| 1532 | // If the client sent a path in folderName (e.g., "bob/new-sub") and parent is root/empty, |
| 1533 | // derive parent = "bob" and folderName = "new-sub" so permission checks hit "bob". |
| 1534 | $normalized = ACL::normalizeFolder($folderName); |
| 1535 | if ( |
| 1536 | $normalized !== 'root' && strpos($normalized, '/') !== false && |
| 1537 | ($parentIn === '' || strcasecmp($parentIn, 'root') === 0) |
| 1538 | ) { |
| 1539 | $parentIn = trim(str_replace('\\', '/', dirname($normalized)), '/'); |
| 1540 | $folderName = basename($normalized); |
| 1541 | if ($parentIn === '' || strcasecmp($parentIn, 'root') === 0) { |
| 1542 | $parentIn = 'root'; |
| 1543 | } |
| 1544 | } |
| 1545 | |
| 1546 | $parent = ($parentIn === '' || strcasecmp($parentIn, 'root') === 0) ? 'root' : $parentIn; |
| 1547 | $folderName = trim($folderName); |
| 1548 | if ($folderName === '') { |
| 1549 | return ['success' => false, 'error' => 'Folder name required']; |
| 1550 | } |
| 1551 | |
| 1552 | $parentParts = self::splitFolderSegments($parent); |
| 1553 | if ($parentParts === null) { |
| 1554 | return ['success' => false, 'error' => 'Invalid parent folder name']; |
| 1555 | } |
| 1556 | $folderParts = self::splitFolderSegments($folderName, false); |
| 1557 | if ($folderParts === null || count($folderParts) !== 1) { |
| 1558 | return ['success' => false, 'error' => 'Invalid folder name']; |
| 1559 | } |
| 1560 | |
| 1561 | $parent = $parentParts === [] ? 'root' : implode('/', $parentParts); |
| 1562 | $folderName = $folderParts[0]; |
| 1563 | |
| 1564 | // ACL key for new folder |
| 1565 | $newKey = ($parent === 'root') ? $folderName : ($parent . '/' . $folderName); |
| 1566 | |
| 1567 | // -------- Compose filesystem paths -------- |
| 1568 | $base = rtrim(self::uploadRoot(), "/\\"); |
| 1569 | $parentRel = ($parent === 'root') ? '' : str_replace('/', DIRECTORY_SEPARATOR, $parent); |
| 1570 | $parentAbs = $parentRel ? ($base . DIRECTORY_SEPARATOR . $parentRel) : $base; |
| 1571 | $storage = self::storage(); |
| 1572 | $isLocal = $storage->isLocal(); |
| 1573 | |
| 1574 | // -------- Exists / sanity checks -------- |
| 1575 | $parentStat = $isLocal ? null : $storage->stat($parentAbs); |
| 1576 | $parentExists = $isLocal |
| 1577 | ? is_dir($parentAbs) |
| 1578 | : ($parentStat !== null && ($parentStat['type'] ?? '') === 'dir'); |
| 1579 | if (!$parentExists) { |
| 1580 | return ['success' => false, 'error' => 'Parent folder does not exist']; |
| 1581 | } |
| 1582 | |
| 1583 | if ($isLocal) { |
no test coverage detected