* Saves tag data for a specified file and updates the global tags. * * @param string $folder The folder where the file is located (e.g., "root" or a subfolder). * @param string $file The name of the file for which tags are being saved. * @param array $tags An array of tag definitions, each being an associative array (e.g. ['name' => 'Tag1', 'color' => '#FF0000']). * @para
(string $folder, string $file, array $tags, bool $deleteGlobal = false, ?string $tagToDelete = null)
| 3498 | * @return array Returns an associative array with a "success" key and updated "globalTags", or an "error" key on failure. |
| 3499 | */ |
| 3500 | public static function saveFileTag(string $folder, string $file, array $tags, bool $deleteGlobal = false, ?string $tagToDelete = null): array |
| 3501 | { |
| 3502 | // Validate the file name and folder |
| 3503 | $folder = trim($folder) ?: 'root'; |
| 3504 | $file = basename(trim($file)); |
| 3505 | if (strtolower($folder) !== 'root' && !preg_match(REGEX_FOLDER_NAME, $folder)) { |
| 3506 | return ["error" => "Invalid folder name."]; |
| 3507 | } |
| 3508 | if (!preg_match(REGEX_FILE_NAME, $file)) { |
| 3509 | return ["error" => "Invalid file name."]; |
| 3510 | } |
| 3511 | |
| 3512 | $tags = is_array($tags) ? $tags : []; |
| 3513 | $tags = self::sanitizeTags($tags); |
| 3514 | |
| 3515 | // Determine the folder metadata file. |
| 3516 | $metadataFile = self::getMetadataFilePath($folder); |
| 3517 | |
| 3518 | // Load existing metadata for this folder. |
| 3519 | $metadata = []; |
| 3520 | if (file_exists($metadataFile)) { |
| 3521 | $metadata = json_decode(file_get_contents($metadataFile), true) ?? []; |
| 3522 | } |
| 3523 | |
| 3524 | // Update the metadata for the specified file. |
| 3525 | if (!isset($metadata[$file])) { |
| 3526 | $metadata[$file] = []; |
| 3527 | } |
| 3528 | $metadata[$file]['tags'] = $tags; |
| 3529 | |
| 3530 | if (file_put_contents($metadataFile, json_encode($metadata, JSON_PRETTY_PRINT), LOCK_EX) === false) { |
| 3531 | return ["error" => "Failed to save tag data for file metadata."]; |
| 3532 | } |
| 3533 | |
| 3534 | // Now update the global tags file. |
| 3535 | $globalTagsFile = self::metaRoot() . "createdTags.json"; |
| 3536 | $globalTags = []; |
| 3537 | if (file_exists($globalTagsFile)) { |
| 3538 | $globalTags = json_decode(file_get_contents($globalTagsFile), true) ?? []; |
| 3539 | if (!is_array($globalTags)) { |
| 3540 | $globalTags = []; |
| 3541 | } |
| 3542 | } |
| 3543 | $globalTags = self::sanitizeTags($globalTags); |
| 3544 | |
| 3545 | // If deleteGlobal is true and tagToDelete is provided, remove that tag. |
| 3546 | if ($deleteGlobal && !empty($tagToDelete)) { |
| 3547 | $tagToDeleteLower = strtolower($tagToDelete); |
| 3548 | $globalTags = array_values(array_filter($globalTags, function ($globalTag) use ($tagToDeleteLower) { |
| 3549 | return strtolower($globalTag['name']) !== $tagToDeleteLower; |
| 3550 | })); |
| 3551 | } else { |
| 3552 | // Otherwise, merge (update or add) new tags into the global tags. |
| 3553 | foreach ($tags as $tag) { |
| 3554 | $found = false; |
| 3555 | foreach ($globalTags as &$globalTag) { |
| 3556 | if (strtolower($globalTag['name']) === strtolower($tag['name'])) { |
| 3557 | $globalTag['color'] = $tag['color']; |
no test coverage detected