PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.0.15
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.0.15
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 1.41 All 42 releases
fluent-boards / app / Hooks / Handlers / ExternalPages.php

ExternalPages.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.0.15, at app/Hooks/Handlers/ExternalPages.php

204 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Hooks\Handlers;
4
5 use FluentBoards\App\Models\Attachment;
6 use FluentBoards\App\App;
7 use FluentBoards\App\Models\CommentImage;
8 use FluentBoards\App\Models\Meta;
9 use FluentBoards\App\Models\Task;
10 use FluentBoards\App\Services\Constant;
11 use FluentBoards\App\Services\Libs\FileSystem;
12
13 class ExternalPages
14 {
15 public function view_uploaded_comment_image()
16 {
17 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public file serving endpoint, security validated via hash
18 $attachmentHash = isset($_REQUEST['fbs_comment_image']) ? sanitize_text_field(wp_unslash($_REQUEST['fbs_comment_image'])) : '';
19
20 if (empty($attachmentHash)) {
21 die(esc_html__('Invalid Attachment Hash', 'fluent-boards'));
22 }
23
24 $attachment = $this->getUploadedImageByHash($attachmentHash);
25
26 if (!$attachment) {
27 die(esc_html__('Invalid Attachment Hash', 'fluent-boards'));
28 }
29
30 $boardId = $this->getAttachmentBoardId($attachment);
31 if (!$boardId) {
32 die(esc_html__('Invalid Attachment Hash', 'fluent-boards'));
33 }
34
35 if ('local' !== $attachment->driver) {
36 if(!empty($attachment->file_path)){
37 $this->redirectToExternalAttachment($attachment->full_url);
38 }else{
39 die(esc_html__('File could not be found', 'fluent-boards'));
40 }
41 return;
42 }
43 $filePath = FileSystem::resolveLocalAttachmentPath($attachment->file_path, $boardId);
44
45 if (!$filePath) {
46 die(esc_html__('File could not be found.', 'fluent-boards'));
47 }
48
49 $this->serveLocalAttachment($attachment, $filePath);
50 }
51
52 public function view_comment_image()
53 {
54 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public file serving endpoint, security validated via hash and signature
55 $attachmentHash = isset($_REQUEST['fbs_comment_image']) ? sanitize_text_field(wp_unslash($_REQUEST['fbs_comment_image'])) : '';
56
57 if (empty($attachmentHash)) {
58 die(esc_html__('Invalid Attachment Hash', 'fluent-boards'));
59 }
60
61 $attachment = $this->getUploadedImageByHash($attachmentHash);
62
63 if (!$attachment) {
64 die(esc_html__('Invalid Attachment Hash', 'fluent-boards'));
65 }
66
67 if (in_array($attachment->object_type, [Constant::COMMENT_IMAGE])) {
68 $attachment->load('comment');
69 } elseif (in_array($attachment->object_type, [Constant::TASK_DESCRIPTION])) {
70 $attachment['task'] = Task::find($attachment->object_id);
71 }
72
73 // check signature hash
74 if (!$this->validateAttachmentSignature($attachment)) {
75 die(esc_html__('Sorry, Your secure sign is invalid, Please reload the previous page and get new signed url', 'fluent-boards'));
76 }
77
78 //If external file
79 if ('local' !== $attachment->driver) {
80 if(!empty($attachment->file_path)){
81 $this->redirectToExternalAttachment($attachment->full_url);
82 }else{
83 die(esc_html__('File could not be found', 'fluent-boards'));
84 }
85 }
86
87 //Handle Local file
88 if (in_array($attachment->object_type, [Constant::COMMENT_IMAGE])) {
89 $fileName = $attachment->file_path;
90 $boardId = $attachment->comment->board_id;
91 } elseif (in_array($attachment->object_type, [Constant::TASK_DESCRIPTION])) {
92 $fileName = $attachment->file_path;
93 $boardId = $attachment->task->board_id;
94 }
95
96 $filePath = $fileName;
97 if(!file_exists($fileName)){
98 $filePath = FileSystem::setSubDir('board_' . $boardId)->getDir() . DIRECTORY_SEPARATOR . $fileName;
99 }
100
101 if (!file_exists($filePath)) {
102 die(esc_html__('File could not be found.', 'fluent-boards'));
103 }
104
105 $this->serveLocalAttachment($attachment, $filePath);
106 }
107
108 private function getUploadedImageByHash($attachmentHash)
109 {
110 return CommentImage::where('file_hash', $attachmentHash)->first();
111 }
112
113 /**
114 * Resolve the owning board from server-side attachment relationships.
115 */
116 private function getAttachmentBoardId($attachment)
117 {
118 if ($attachment->object_type === Constant::COMMENT_IMAGE) {
119 $attachment->load('comment');
120 if (!empty($attachment->comment->board_id)) {
121 return absint($attachment->comment->board_id);
122 }
123
124 if (empty($attachment->object_id)) {
125 $settings = is_array($attachment->settings) ? $attachment->settings : [];
126 return empty($settings['board_id']) ? 0 : absint($settings['board_id']);
127 }
128
129 return 0;
130 }
131
132 if ($attachment->object_type === Constant::TASK_DESCRIPTION) {
133 $task = Task::find($attachment->object_id);
134 return !empty($task->board_id) ? absint($task->board_id) : 0;
135 }
136
137 if ($attachment->object_type === Constant::BOARD_BACKGROUND_IMAGE) {
138 return empty($attachment->object_id) ? 0 : absint($attachment->object_id);
139 }
140
141 return 0;
142 }
143
144 private function serveLocalAttachment($attachment, $filePath)
145 {
146 ob_get_clean();
147 header("Content-Type: {$attachment->attachment_type}");
148 header("Content-Disposition: inline; filename=\"{$attachment->title}\"");;
149 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Serving binary file content directly to browser, WP_Filesystem not suitable for this use case
150 readfile($filePath);
151 die();
152 }
153
154 private function validateAttachmentSignature($attachment)
155 {
156 $sign = md5($attachment->id . gmdate('YmdH'));
157 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Signature validation serves as security mechanism
158 $requestSign = isset($_REQUEST['secure_sign']) ? sanitize_text_field(wp_unslash($_REQUEST['secure_sign'])) : '';
159 return $sign === $requestSign;
160 }
161
162 public function redirectToPage()
163 {
164 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public redirect endpoint, no sensitive operations
165 $taskId = isset($_GET['taskId']) ? absint(wp_unslash($_GET['taskId'])) : 0;
166
167 if (!$taskId) {
168 wp_die(esc_html__('Invalid task ID', 'fluent-boards'));
169 }
170
171 $task = Task::findOrFail($taskId);
172 if ($this->isFrontendEnabled() == 'no') {
173 $urlBase = apply_filters('fluent_boards/app_url', admin_url('admin.php?page=fluent-boards#/'));
174 $page_url = $urlBase . 'boards/' . $task->board_id . '/tasks/' . $task->id . '-' .substr($task->title, 0, 10);
175 wp_redirect($page_url);
176 exit;
177 } else {
178 $urlBase = apply_filters('fluent_boards/app_url', admin_url('admin.php?page=fluent-boards#/'));
179 $page_url = $urlBase . 'boards/' . $task->board_id . '/tasks/' . $task->id . '-' .substr($task->title, 0, 10);
180 wp_redirect($page_url);
181 exit;
182 }
183
184 die();
185 }
186
187 private function isFrontendEnabled()
188 {
189 $storedSettings = get_option('fluent_boards_modules', []);
190 $settings = is_string($storedSettings) ? maybe_unserialize($storedSettings) : $storedSettings;
191
192 if (is_array($settings) && isset($settings['frontend']['enabled'])) {
193 return $settings['frontend']['enabled'];
194 }
195
196 return 'no';
197 }
198 private function redirectToExternalAttachment($redirectUrl)
199 {
200 wp_redirect($redirectUrl, 307);
201 exit();
202 }
203 }
204