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
fluent-boards / app / Hooks / actions.php

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

282 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 //if accessed directly exit
3 if (!defined('ABSPATH')) exit;
4
5 use FluentBoards\App\Services\Helper;
6 use FluentBoards\App\Services\Libs\FileSystem;
7
8 /**
9 * All registered action's handlers should be in app\Hooks\Handlers,
10 * addAction is similar to add_action and addCustomAction is just a
11 * wrapper over add_action which will add a prefix to the hook name
12 * using the plugin slug to make it unique in all WordPress plugins,
13 * ex: $app->addCustomAction('foo', ['FooHandler', 'handleFoo']) is
14 * equivalent to add_action('slug-foo', ['FooHandler', 'handleFoo']).
15 */
16
17 /**
18 * @var $app FluentBoards\Framework\Foundation\Application
19 */
20
21 (new \FluentBoards\App\Hooks\Handlers\AdminMenuHandler())->register();
22 $app->addAction('admin_init', 'UpdateHandler@maybeUpgradeIndexes', 10, 0);
23
24 add_action('fluent_boards/check_upload_protection', function () {
25 FileSystem::checkUploadProtection();
26 });
27
28 // Cover existing uploads after updates, even before another file is uploaded.
29 add_action('admin_init', function () {
30 if (!current_user_can('manage_options') || wp_doing_ajax()) {
31 return;
32 }
33
34 $protectionReady = FileSystem::installUploadProtectionRules(false);
35 $blocked = FileSystem::checkUploadProtection(false);
36 if (!$protectionReady || $blocked !== true) {
37 add_action('admin_notices', function () use ($protectionReady, $blocked) {
38 if ($blocked === false) {
39 $message = __('Fluent Boards uploads are accessible by direct URL without a login. Ask your hosting provider to block direct web access to this directory and its subdirectories.', 'fluent-boards');
40 } elseif (!$protectionReady) {
41 $message = __('Fluent Boards could not prepare upload-directory protection. Ask your hosting provider to check filesystem permissions and block direct access to uploads.', 'fluent-boards');
42 } else {
43 $message = __('Fluent Boards could not verify upload-directory protection. Writing .htaccess rules does not confirm that your web server enforces them.', 'fluent-boards');
44 }
45 $uploads = wp_upload_dir();
46 $folder = apply_filters('fluent_boards/upload_folder_name', FLUENT_BOARDS_UPLOAD_DIR);
47 $url = trailingslashit($uploads['baseurl']) . trim($folder, '/');
48 echo '<div class="notice notice-warning"><p>' . esc_html($message) . '</p><p>'
49 . esc_html__('Nginx requires a server rule denying this upload URL and every path beneath it. Apache must honor the generated .htaccess authorization rules. Verify that direct file requests return 403 or 404. This check retries every five minutes.', 'fluent-boards')
50 . '</p><p><code>' . esc_html($url) . '</code></p></div>';
51 });
52 }
53 });
54
55 /*
56 * Add custom CSS for fbs_notice.
57 */
58 add_action('admin_head', function () {
59 echo '<style>
60 .fbs_notice {
61 background: var(--fbs-primary-bg, #ffffff);
62 border: 1px solid var(--fbs-primary-border, #E1E4EA);
63 border-left: 3px solid #FB3748;
64 padding: 10px 12px !important;
65 border-radius: 8px;
66 margin-bottom: 5px;
67 }
68 </style>';
69 });
70
71 //$app->addCustomAction('task_prop_changed', 'ActivityHandler@logActivity', 10, 3);
72 $app->addCustomAction('task_board_changed', 'ActivityHandler@logMoveTaskToAnotherBoardActivity', 10, 2); //will call from service after board change code merged
73 $app->addCustomAction('task_due_date_changed', 'ActivityHandler@logDueDateActivity', 10, 2);
74 $app->addCustomAction('task_due_date_removed', 'ActivityHandler@logDueDateRemoveActivity', 10, 1);
75 $app->addCustomAction('task_start_date_changed', 'ActivityHandler@logStartDateActivity', 10, 2);
76
77 $app->addCustomAction('task_priority_changed', 'ActivityHandler@logPriorityChangeActivity', 10, 2);
78 $app->addCustomAction('comment_created', 'ActivityHandler@logCommentCreateActivity', 10, 1);
79 $app->addCustomAction('comment_updated', 'ActivityHandler@logCommentUpdateActivity', 10, 2);
80 $app->addCustomAction('comment_deleted', 'ActivityHandler@logCommentDeleteActivity', 10, 1);
81 $app->addCustomAction('subtask_added', 'ActivityHandler@logSubtaskAddedActivity', 10, 2);
82 $app->addCustomAction('subtask_cloned', 'ActivityHandler@logSubtaskCloneActivity', 10, 2);
83 $app->addCustomAction('subtask_group_created', 'ActivityHandler@logSubtaskGroupAddedActivity', 10, 2);
84 $app->addCustomAction('subtask_deleted_activity', 'ActivityHandler@logSubtaskDeletedActivity', 10, 2);
85 $app->addCustomAction('subtask_group_deleted_activity', 'ActivityHandler@logSubtaskGroupDeletedActivity', 10, 2);
86 $app->addCustomAction('subtask_group_title_updated', 'ActivityHandler@logSubtaskGroupTitleUpdatedActivity', 10, 2);
87 $app->addCustomAction('task_completed_activity', 'ActivityHandler@logTaskCompletedOrReopenActivity', 10, 2);
88 $app->addCustomAction('task_stage_updated', 'ActivityHandler@logTaskStageUpdatedActivity', 10, 3);
89 $app->addCustomAction('task_assignee_added', 'ActivityHandler@logAssigneeAddedActivity', 10, 2);
90 $app->addCustomAction('task_assignee_removed', 'ActivityHandler@logAssigneeRemovedActivity', 10, 2);
91 $app->addCustomAction('task_added_from_fluent_form', 'ActivityHandler@taskAddedFromFluentForms', 10, 1);
92 $app->addCustomAction('repeat_task_created', 'ActivityHandler@logRepeatTaskCreatedActivity', 10, 2);
93 $app->addCustomAction('repeat_task_set', 'ActivityHandler@logRepeatTaskSet', 10, 1);
94 $app->addCustomAction('repeat_task_updated', 'ActivityHandler@logRepeatTaskUpdated', 10, 1);
95 $app->addCustomAction('task_created', 'ActivityHandler@logTaskCreationActivity', 10, 1);
96 $app->addCustomAction('task_content_updated', 'ActivityHandler@logTaskContentUpdatedActivity', 10, 3);
97 $app->addCustomAction('task_deleted', 'TaskHandler@taskDeleted', 10, 1);
98 $app->addCustomAction('task_archived', 'ActivityHandler@taskArchived', 10, 1);
99 $app->addCustomAction('task_custom_field_changed', 'ActivityHandler@logCustomFieldActivity', 10, 5);
100 $app->addCustomAction('board_custom_field_created', 'BoardHandler@customFieldCreated', 10, 1);
101 $app->addCustomAction('board_custom_field_updated', 'BoardHandler@customFieldUpdated', 10, 2);
102
103 $app->addCustomAction('board_created', 'BoardHandler@boardCreated', 10, 1);
104 $app->addCustomAction('board_created_from_automation', 'BoardHandler@boardCreatedFromAutomation', 10, 1);
105 $app->addCustomAction('before_board_deleted', 'BoardHandler@beforeBoardDeleted', 10, 2);
106 $app->addCustomAction('board_updated', 'BoardHandler@boardUpdated', 10, 2);
107 $app->addCustomAction('stage_updated', 'BoardHandler@boardStageUpdated', 10, 3);
108 $app->addCustomAction('board_stage_moved', 'BoardHandler@boardStageMoved', 10, 2);
109 $app->addCustomAction('board_stage_added', 'BoardHandler@boardStageAdded', 10, 2);
110 $app->addCustomAction('stage_deleted', 'BoardHandler@boardStageDeleted', 10, 2);
111 $app->addCustomAction('stage_archived', 'BoardHandler@boardStageArchived', 10, 2);
112 $app->addCustomAction('board_stage_restored', 'BoardHandler@boardArchivedStageRestore', 10, 2);
113 $app->addCustomAction('task_created', 'BoardHandler@taskCreatedOnBoard', 10, 1);
114 $app->addCustomAction('before_task_deleted', 'BoardHandler@beforeTaskDeleted', 10, 2);
115 $app->addCustomAction('task_deleted', 'BoardHandler@taskdeleted', 10, 1);
116 $app->addCustomAction('task_moved_from_board', 'BoardHandler@taskMovedFromBoard', 10, 3);
117 $app->addCustomAction('task_moved_from_board', 'ActivityHandler@taskMovedFromBoard', 10, 3);
118 $app->addCustomAction('task_archived', 'BoardHandler@taskArchivedOnBoard', 10, 1);
119 $app->addCustomAction('tasks_moved_between_stages', 'BoardHandler@tasksMovedBetweenStages', 10, 4);
120 $app->addCustomAction('board_member_added', 'BoardHandler@boardMemberAdded', 10, 2);
121 $app->addCustomAction('board_viewer_added', 'BoardHandler@boardViewerAdded', 10, 2);
122 $app->addCustomAction('board_member_removed', 'BoardHandler@boardMemberRemoved', 10, 2);
123 $app->addCustomAction('board_admin_added', 'BoardHandler@boardAdminAdded', 10, 2);
124 $app->addCustomAction('board_admin_removed', 'BoardHandler@boardAdminRemoved', 10, 2);
125 $app->addCustomAction('associate_user_add_change_remove_activity', 'ActivityHandler@associateUserAddChangeRemoveActivity', 10, 3);
126 $app->addCustomAction('task_assignee_leave', 'ActivityHandler@taskAssigneeLeave', 10, 1);
127 $app->addCustomAction('task_assignee_join', 'ActivityHandler@taskAssigneeJoin', 10, 1);
128 $app->addCustomAction('task_label', 'ActivityHandler@taskLabelActivity', 10, 3);
129 $app->addCustomAction('label_manage_for_task_activity', 'ActivityHandler@labelManageForTaskActivity', 10, 2);
130 $app->addCustomAction('board_label_created', 'BoardHandler@boardLabelCreatedActivity', 10, 1);
131 $app->addCustomAction('board_label_updated', 'BoardHandler@boardLabelUpdatedActivity', 10, 1);
132 $app->addCustomAction('board_label_deleted', 'BoardHandler@boardLabelDeletedActivity', 10, 1);
133 $app->addCustomAction('board_stages_reordered', 'BoardHandler@boardStagesReOrdered', 10, 2);
134 $app->addCustomAction('default_assignees_updated', 'BoardHandler@defaultAssigneesUpdated', 10, 2);
135
136 $app->addCustomAction('comment_created', 'NotificationHandler@addCommentNotification', 10, 1);
137 $app->addCustomAction('mention_comment_notification', 'NotificationHandler@mentionInCommentNotification', 10, 2);
138 $app->addCustomAction('subtask_added', 'NotificationHandler@addSubtaskNotification', 10, 2);
139 $app->addCustomAction('task_due_date_changed', 'NotificationHandler@changeDueDateNotification', 10, 2);
140 $app->addCustomAction('task_start_date_changed', 'NotificationHandler@changeStartDateNotification', 10, 2);
141 $app->addCustomAction('task_stage_updated', 'NotificationHandler@changeStageNotification', 10, 2);
142 $app->addCustomAction('task_priority_changed', 'NotificationHandler@changePriorityNotification', 10, 2);
143 $app->addCustomAction('task_archived', 'NotificationHandler@taskArchiveNotification', 10, 1);
144 $app->addCustomAction('task_content_updated', 'NotificationHandler@changeTitleNotification', 10, 3);
145 //$app->addCustomAction('description_changed_notification', 'NotificationHandler@changeDescriptionNotification', 10, 1);
146 $app->addCustomAction('board_changed_notification', 'NotificationHandler@changeBoardNotification', 10, 2);
147 $app->addCustomAction('task_assignee_added', 'NotificationHandler@assigneeAddedNotification', 10, 2);
148 $app->addCustomAction('task_assignee_removed', 'NotificationHandler@assigneeRemovedNotification', 10, 2);
149
150 $app->addCustomAction('one_time_schedule_send_email_for_comment', 'ScheduleHandler@sendEmailForComment', 10, 3);
151 $app->addCustomAction('one_time_schedule_send_email_for_mention', 'ScheduleHandler@sendEmailForMention', 10, 3);
152 $app->addCustomAction('one_time_schedule_send_email_for_add_assignee', 'ScheduleHandler@sendEmailForAddAssignee', 10, 3);
153 $app->addCustomAction('one_time_schedule_send_email_for_remove_assignee', 'ScheduleHandler@sendEmailForRemoveAssignee', 10, 3);
154 $app->addCustomAction('one_time_schedule_send_email_for_stage_change', 'ScheduleHandler@sendEmailForChangeStage', 10, 3);
155
156 //Todo:: update handler
157 $app->addCustomAction('one_time_schedule_send_email_for_due_date_update', 'ScheduleHandler@sendEmailForDueDateUpdate', 10, 3);
158 $app->addCustomAction('one_time_schedule_send_email_for_task_archived', 'ScheduleHandler@sendEmailForArchivedTask', 10, 3);
159 $app->addCustomAction('one_time_schedule_send_email_for_removed_from_task', 'ScheduleHandler@sendEmailForChangeStage', 10, 3);
160
161 $app->addCustomAction('task_attachment_added', 'ActivityHandler@taskAttachmentAdded', 10, 1);
162 $app->addCustomAction('task_attachment_deleted', 'ActivityHandler@taskAttachmentDeleted', 10, 1);
163 $app->addCustomAction('task_attachment_deleted', 'TaskHandler@taskAttachmentDeleted', 10, 2);
164 // Removed: direct call is made from Comment::deleting to TaskHandler@commentImageDeleted
165 $app->addAction('deleted_user', 'BoardHandler@deleteUserRelatedData', 10, 3);
166 $app->addAction('fluent_boards/delete_remote_attachment_files', 'FileHandler@deleteRemoteAttachmentFiles', 10, 1);
167
168 $app->addCustomAction('task_created', 'TaskHandler@onTaskCreated', 10, 1);
169 $app->addCustomAction('comment_created', 'TaskHandler@onCommentCreated', 10, 1);
170 $app->addCustomAction('assign_another_user', 'TaskHandler@onAssignAnotherUser', 10, 2);
171 $app->addCustomAction('board_background_updated', 'BoardHandler@backgroundUpdated', 10, 2);
172 $app->addCustomAction('task_cloned', 'TaskHandler@taskCloned', 10, 2);
173 $app->addCustomAction('task_cloned_activity', 'ActivityHandler@taskCloned', 10, 2);
174
175 if (defined('WP_CLI') && WP_CLI) {
176 \WP_CLI::add_command('fluent_boards', '\FluentBoards\App\Hooks\Cli\Commands');
177 }
178
179
180 add_action('init', function () {
181 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public routing parameter check, no data modification
182 if (!isset($_REQUEST['fbs'])) {
183 return;
184 }
185 });
186
187 /*
188 * IMPORTANT
189 * External Pages Handler
190 * Each Request must have fbs=1 as a query parameter, then the plugin will handle the request.
191 */
192 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public routing logic, security validated in individual handlers
193 if(isset($_GET['fbs']) && $_GET['fbs'] == 1) {
194
195 // For viewing attachment
196 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public routing logic, security validated in handler
197 if(isset($_GET['route']) && ($_GET['route'] == 'task')) {
198 add_action('init', function() {
199 (new \FluentBoardsPro\App\Hooks\Handlers\ExternalPages())->handleTaskWebhook();
200 });
201 }
202
203 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public file serving endpoint, security validated by hash in handler
204 if(isset($_GET['fbs_comment_image'])) {
205 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public routing check, security validated in handler
206 if(isset($_GET['fbs_type']) && $_GET['fbs_type'] == 'public_url') {
207 add_action('init', function() {
208 (new \FluentBoards\App\Hooks\Handlers\ExternalPages())->view_uploaded_comment_image();
209 });
210 } else {
211 add_action('init', function() {
212 (new \FluentBoards\App\Hooks\Handlers\ExternalPages())->view_comment_image();
213 });
214 }
215
216 }
217
218 }
219
220 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public redirect endpoint, no sensitive operations
221 if(isset($_GET['redirect']) && $_GET['redirect'] == 'to_task') {
222 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public redirect endpoint, validation performed in handler
223 if (isset($_GET['taskId'])) {
224 add_action('init', function() {
225 (new \FluentBoards\App\Hooks\Handlers\ExternalPages())->redirectToPage();
226 });
227 }
228 }
229 // Stage Archive Handler - Archive tasks when stage is archived, restore when stage is restored
230 $app->addAction('fluent_boards/stage_archived_with_tasks', 'StageArchiveHandler@onStageArchived', 10, 2);
231 $app->addAction('fluent_boards/stage_restored_with_tasks', 'StageArchiveHandler@onStageRestored', 10, 2);
232
233 /*
234 * MCP - Register Fluent Boards abilities for the WordPress Abilities API.
235 *
236 * This mirrors FluentCRM's MCP architecture: on WordPress installs without the
237 * Abilities API / WP MCP Adapter, `wp_register_ability` is undefined and the
238 * integration skips silently. Admins can opt out with the `mcp_enabled` option.
239 */
240 add_filter('fluent_kit/mcp_products', [\FluentBoards\App\Modules\MCP\MCPInit::class, 'registerToolkitProduct'], 10, 2);
241 add_filter('fluent_kit/mcp_toggle_handlers', function ($handlers) {
242 $handlers['fluent-boards'] = [
243 'get_enabled' => function () {
244 return fluent_boards_get_option('mcp_enabled', 'yes') === 'yes';
245 },
246 'set_enabled' => function ($enabled) {
247 fluent_boards_update_option('mcp_enabled', $enabled ? 'yes' : 'no');
248 },
249 ];
250
251 return $handlers;
252 });
253
254 \FluentBoards\App\Services\MCPAccessService::registerHooks();
255
256 add_action('init', function () {
257 if (!function_exists('wp_register_ability')) {
258 return;
259 }
260
261 if (fluent_boards_get_option('mcp_enabled', 'yes') !== 'yes') {
262 return;
263 }
264
265 (new \FluentBoards\App\Modules\MCP\MCPInit())->init();
266 }, 5);
267
268 $invalidateMcpContext = [\FluentBoards\App\Modules\MCP\Tools\ContextTools::class, 'invalidateCache'];
269 foreach ([
270 'fluent_boards/board_created',
271 'fluent_boards/board_updated',
272 'fluent_boards/before_board_deleted',
273 'fluent_boards/board_stage_added',
274 'fluent_boards/stage_updated',
275 'fluent_boards/stage_deleted',
276 'fluent_boards/board_label_created',
277 'fluent_boards/board_label_updated',
278 'fluent_boards/board_label_deleted',
279 ] as $mcpContextHook) {
280 add_action($mcpContextHook, $invalidateMcpContext);
281 }
282