PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.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 1.41 All 42 releases
← All changes | app/Hooks/Handlers/ExternalPages.php +117 -5 1.212.1.0 View file →
@@ -1,13 +1,125 @@
1 1 <?php
2 2
3 3 namespace FluentBoards\App\Hooks\Handlers;
4 4
5 -use FluentBoards\App\Models\Attachment;
6 -use FluentBoards\App\App;
7 -use FluentBoards\App\Models\Meta;
8 -use FluentBoards\App\Services\Constant;
5 +use FluentBoards\App\Models\CommentImage;
6 +use FluentBoards\App\Models\Task;
7 +use FluentBoards\App\Services\AttachmentAccessService;
8 +use FluentBoards\App\Services\Libs\FileSystem;
9 9
10 10 class ExternalPages
11 11 {
12 + public function view_uploaded_comment_image()
13 + {
14 + nocache_headers();
15 + header('Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0');
12 16
13 -}
17 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only endpoint; current permissions are checked for every request
18 + $attachmentHash = isset($_REQUEST['fbs_comment_image']) && is_string($_REQUEST['fbs_comment_image'])
19 + ? sanitize_text_field(wp_unslash($_REQUEST['fbs_comment_image'])) : '';
20 +
21 + if (empty($attachmentHash)) {
22 + wp_die(esc_html__('Invalid Attachment Hash', 'fluent-boards'), '', ['response' => 404]);
23 + }
24 +
25 + $attachment = $this->getUploadedImageByHash($attachmentHash);
26 +
27 + if (!$attachment) {
28 + wp_die(esc_html__('Invalid Attachment Hash', 'fluent-boards'), '', ['response' => 404]);
29 + }
30 +
31 + $boardId = (new AttachmentAccessService())->getAccessibleBoardId($attachment);
32 + if (!$boardId) {
33 + wp_die(esc_html__('You do not have permission to view this image.', 'fluent-boards'), '', ['response' => 403]);
34 + }
35 +
36 + // Old URLs may include a board ID, but it can never choose the file directory.
37 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Optional compatibility parameter is checked against the attachment's board
38 + if (isset($_REQUEST['fbs_bid']) && (!is_scalar($_REQUEST['fbs_bid']) || absint(wp_unslash($_REQUEST['fbs_bid'])) !== $boardId)) {
39 + wp_die(esc_html__('You do not have permission to view this image.', 'fluent-boards'), '', ['response' => 403]);
40 + }
41 +
42 + if ('local' !== $attachment->driver) {
43 + if(!empty($attachment->file_path)){
44 + $this->redirectToExternalAttachment($attachment->full_url);
45 + }else{
46 + wp_die(esc_html__('File could not be found', 'fluent-boards'), '', ['response' => 404]);
47 + }
48 + return;
49 + }
50 +
51 + // Preserve legacy absolute paths inside plugin uploads; bare filenames use the trusted board.
52 + $filePath = FileSystem::resolveLocalAttachmentPath($attachment->file_path)
53 + ?: FileSystem::resolveLocalAttachmentPath($attachment->file_path, $boardId);
54 +
55 + if (!$filePath || !is_readable($filePath)) {
56 + wp_die(esc_html__('File could not be found.', 'fluent-boards'), '', ['response' => 404]);
57 + }
58 +
59 + $this->serveLocalAttachment($attachment, $filePath);
60 + }
61 +
62 + public function view_comment_image()
63 + {
64 + $this->view_uploaded_comment_image();
65 + }
66 +
67 + private function getUploadedImageByHash($attachmentHash)
68 + {
69 + return CommentImage::where('file_hash', $attachmentHash)->first();
70 + }
71 +
72 + private function serveLocalAttachment($attachment, $filePath)
73 + {
74 + if (ob_get_level()) {
75 + ob_end_clean();
76 + }
77 + header("Content-Type: {$attachment->attachment_type}");
78 + header("Content-Disposition: inline; filename=\"{$attachment->title}\"");
79 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Serving binary file content directly to browser, WP_Filesystem not suitable for this use case
80 + readfile($filePath);
81 + die();
82 + }
83 +
84 + public function redirectToPage()
85 + {
86 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public redirect endpoint, no sensitive operations
87 + $taskId = isset($_GET['taskId']) ? absint(wp_unslash($_GET['taskId'])) : 0;
88 +
89 + if (!$taskId) {
90 + wp_die(esc_html__('Invalid task ID', 'fluent-boards'));
91 + }
92 +
93 + $task = Task::findOrFail($taskId);
94 + if ($this->isFrontendEnabled() == 'no') {
95 + $urlBase = apply_filters('fluent_boards/app_url', admin_url('admin.php?page=fluent-boards#/'));
96 + $page_url = $urlBase . 'boards/' . $task->board_id . '/tasks/' . $task->id . '-' .substr($task->title, 0, 10);
97 + wp_redirect($page_url);
98 + exit;
99 + } else {
100 + $urlBase = apply_filters('fluent_boards/app_url', admin_url('admin.php?page=fluent-boards#/'));
101 + $page_url = $urlBase . 'boards/' . $task->board_id . '/tasks/' . $task->id . '-' .substr($task->title, 0, 10);
102 + wp_redirect($page_url);
103 + exit;
104 + }
105 +
106 + die();
107 + }
108 +
109 + private function isFrontendEnabled()
110 + {
111 + $storedSettings = get_option('fluent_boards_modules', []);
112 + $settings = is_string($storedSettings) ? maybe_unserialize($storedSettings) : $storedSettings;
113 +
114 + if (is_array($settings) && isset($settings['frontend']['enabled'])) {
115 + return $settings['frontend']['enabled'];
116 + }
117 +
118 + return 'no';
119 + }
120 + private function redirectToExternalAttachment($redirectUrl)
121 + {
122 + wp_redirect($redirectUrl, 307);
123 + exit();
124 + }
125 +}