PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.2.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.2.0
2.2.0 2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 All 43 releases
← All changes | app/Hooks/Handlers/FileHandler.php +108 -22 2.0.4 → 2.2.0 View file →
@@ -32,41 +32,127 @@
32 32 );
33 33 }
34 34
35 35 /**
36 - * Summary of deleteFileByUrl
37 - * @param mixed $file_url
36 + * Delete an attachment's stored file once no attachment row references it.
37 + *
38 + * Local files are removed here, but only when the stored path belongs to
39 + * Fluent Boards. Files on a cloud driver are handed to the storage add-on
40 + * through the `fluent_boards/delete_remote_attachment_file` action, because
41 + * the core plugin holds no bucket credentials.
42 + *
43 + * @param mixed $attachment Attachment row or snapshot (driver, file_path, attachment_type).
44 + * @param int|null $boardId
45 + * @param array|null $sharedReferences Preloaded local references from getSharedLocalReferences().
38 46 * @return bool
39 47 */
40 - public function deleteFileByUrl($file_url)
48 + public function deleteAttachmentFile($attachment, $boardId = null, $sharedReferences = null)
41 49 {
42 - $exists = Attachment::where('full_url', $file_url)->exists();
43 - if($exists) {
44 - return;
50 + if (
51 + !$attachment ||
52 + $attachment->attachment_type === 'url' ||
53 + empty($attachment->file_path)
54 + ) {
55 + return false;
45 56 }
46 - // Convert the URL to the local file path
47 - $upload_dir = wp_upload_dir();
48 - $file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file_url);
49 57
50 - // Check if the file exists
51 - if (file_exists($file_path)) {
52 - // Delete the file
53 - $deleted = wp_delete_file($file_path);
58 + if (!empty($attachment->driver) && $attachment->driver !== 'local') {
59 + // Cloned tasks share the remote object, so keep it while another row points at it.
60 + if (Attachment::where('file_path', $attachment->file_path)->exists()) {
61 + return false;
62 + }
54 63
55 - // Optionally, you can also remove the file from the media library
56 - // Note: This won't delete the file physically, but it will remove it from the media library
57 - $attachment_id = attachment_url_to_postid($file_url);
58 - if ($attachment_id) {
59 - wp_delete_attachment($attachment_id, true);
64 + do_action('fluent_boards/delete_remote_attachment_file', $attachment);
65 +
66 + return true;
67 + }
68 +
69 + $storedFilename = rawurldecode((string) $attachment->file_path);
70 + $isBareFilename = $storedFilename !== ''
71 + && strpos($storedFilename, '/') === false
72 + && strpos($storedFilename, '\\') === false;
73 + $filePath = FileSystem::resolveLocalAttachmentPath($attachment->file_path, $boardId);
74 +
75 + if (!$filePath) {
76 + return false;
77 + }
78 +
79 + // Legacy filenames need the board-qualified URL to distinguish same-named files across boards.
80 + if ($isBareFilename) {
81 + if (
82 + empty($attachment->full_url) ||
83 + $this->hasSharedLocalReference('full_url', $attachment->full_url, $sharedReferences)
84 + ) {
85 + return false;
60 86 }
87 + } elseif ($this->hasSharedLocalReference('file_path', $attachment->file_path, $sharedReferences)) {
88 + return false;
89 + }
61 90
62 - // Return true if the file was successfully deleted
63 - return $deleted;
91 + return (bool) wp_delete_file($filePath);
92 + }
93 +
94 + /**
95 + * Batch the shared-reference lookups deleteAttachmentFile() makes for local files.
96 + *
97 + * @param iterable $attachments Deleted attachment rows or snapshots.
98 + * @return array ['file_path' => [value => true], 'full_url' => [value => true]]
99 + */
100 + public function getSharedLocalReferences($attachments)
101 + {
102 + $values = ['file_path' => [], 'full_url' => []];
103 + foreach ($attachments as $attachment) {
104 + if (!empty($attachment->file_path)) {
105 + $values['file_path'][] = (string) $attachment->file_path;
106 + }
107 + if (!empty($attachment->full_url)) {
108 + $values['full_url'][] = (string) $attachment->full_url;
109 + }
64 110 }
65 - // Return false if the file does not exist
66 - return false;
111 +
112 + $references = ['file_path' => [], 'full_url' => []];
113 + foreach ($values as $column => $columnValues) {
114 + foreach (array_chunk(array_values(array_unique($columnValues)), 200) as $chunk) {
115 + foreach (Attachment::whereIn($column, $chunk)->pluck($column)->toArray() as $value) {
116 + $references[$column][(string) $value] = true;
117 + }
118 + }
119 + }
120 +
121 + return $references;
67 122 }
68 123
124 + private function hasSharedLocalReference($column, $value, $sharedReferences)
125 + {
126 + if (is_array($sharedReferences)) {
127 + return isset($sharedReferences[$column][(string) $value]);
128 + }
129 +
130 + return Attachment::where($column, $value)->exists();
131 + }
132 +
133 +
134 + /**
135 + * Action Scheduler callback: delete the remote files of attachment rows that
136 + * were removed in bulk (a deleted board), outside the request that deleted them.
137 + *
138 + * @param array $files List of ['file_path' => string, 'driver' => string].
139 + * @return void
140 + */
141 + public function deleteRemoteAttachmentFiles($files)
142 + {
143 + foreach ((array) $files as $file) {
144 + if (!is_array($file) || empty($file['file_path']) || empty($file['driver'])) {
145 + continue;
146 + }
147 +
148 + $this->deleteAttachmentFile((object) [
149 + 'attachment_type' => 'file',
150 + 'driver' => sanitize_key($file['driver']),
151 + 'file_path' => (string) $file['file_path'],
152 + ]);
153 + }
154 + }
69 155
70 156 /**
71 157 * Summary of isFileTypeSupported checking file type it will allow only file which is readable by browser
72 158 * @param mixed $file