| @@ -2,8 +2,9 @@ | ||
| 2 | 2 | //if accessed directly exit |
| 3 | 3 | if (!defined('ABSPATH')) exit; |
| 4 | 4 | |
| 5 | 5 | use FluentBoards\App\Services\Helper; |
| 6 | +use FluentBoards\App\Services\Libs\FileSystem; | |
| 6 | 7 | |
| 7 | 8 | /** |
| 8 | 9 | * All registered action's handlers should be in app\Hooks\Handlers, |
| 9 | 10 | * addAction is similar to add_action and addCustomAction is just a |
| @@ -17,43 +18,108 @@ | ||
| 17 | 18 | * @var $app FluentBoards\Framework\Foundation\Application |
| 18 | 19 | */ |
| 19 | 20 | |
| 20 | 21 | (new \FluentBoards\App\Hooks\Handlers\AdminMenuHandler())->register(); |
| 22 | +$app->addAction('admin_init', 'UpdateHandler@maybeUpgradeIndexes', 10, 0); | |
| 21 | 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 | + | |
| 22 | 71 | //$app->addCustomAction('task_prop_changed', 'ActivityHandler@logActivity', 10, 3); |
| 23 | 72 | $app->addCustomAction('task_board_changed', 'ActivityHandler@logMoveTaskToAnotherBoardActivity', 10, 2); //will call from service after board change code merged |
| 24 | -$app->addCustomAction('task_date_changed', 'ActivityHandler@logDueDateActivity', 10, 3); | |
| 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 | + | |
| 25 | 77 | $app->addCustomAction('task_priority_changed', 'ActivityHandler@logPriorityChangeActivity', 10, 2); |
| 26 | 78 | $app->addCustomAction('comment_created', 'ActivityHandler@logCommentCreateActivity', 10, 1); |
| 27 | -$app->addCustomAction('comment_updated', 'ActivityHandler@logCommentUpdateActivity', 10, 3); | |
| 28 | -$app->addCustomAction('comment_deleted', 'ActivityHandler@logCommentDeleteActivity', 10, 2); | |
| 79 | +$app->addCustomAction('comment_updated', 'ActivityHandler@logCommentUpdateActivity', 10, 2); | |
| 80 | +$app->addCustomAction('comment_deleted', 'ActivityHandler@logCommentDeleteActivity', 10, 1); | |
| 29 | 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); | |
| 30 | 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); | |
| 31 | 87 | $app->addCustomAction('task_completed_activity', 'ActivityHandler@logTaskCompletedOrReopenActivity', 10, 2); |
| 32 | -$app->addCustomAction('task_moved_to_new_stage', 'ActivityHandler@logTaskStageUpdatedActivity', 10, 2); | |
| 33 | -$app->addCustomAction('task_assignee_changed', 'ActivityHandler@logAssigneeChangeActivity', 10, 3); | |
| 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); | |
| 34 | 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); | |
| 35 | 95 | $app->addCustomAction('task_created', 'ActivityHandler@logTaskCreationActivity', 10, 1); |
| 36 | 96 | $app->addCustomAction('task_content_updated', 'ActivityHandler@logTaskContentUpdatedActivity', 10, 3); |
| 37 | 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); | |
| 38 | 102 | |
| 39 | 103 | $app->addCustomAction('board_created', 'BoardHandler@boardCreated', 10, 1); |
| 104 | +$app->addCustomAction('board_created_from_automation', 'BoardHandler@boardCreatedFromAutomation', 10, 1); | |
| 40 | 105 | $app->addCustomAction('before_board_deleted', 'BoardHandler@beforeBoardDeleted', 10, 2); |
| 41 | -$app->addCustomAction('board_deleted', 'BoardHandler@boardDeleted', 10, 1); | |
| 42 | 106 | $app->addCustomAction('board_updated', 'BoardHandler@boardUpdated', 10, 2); |
| 43 | -$app->addCustomAction('board_stage_updated', 'BoardHandler@boardStageUpdated', 10, 3); | |
| 107 | +$app->addCustomAction('stage_updated', 'BoardHandler@boardStageUpdated', 10, 3); | |
| 44 | 108 | $app->addCustomAction('board_stage_moved', 'BoardHandler@boardStageMoved', 10, 2); |
| 45 | -$app->addCustomAction('board_stage_dragged', 'BoardHandler@boardStageDragged', 10, 1); | |
| 46 | 109 | $app->addCustomAction('board_stage_added', 'BoardHandler@boardStageAdded', 10, 2); |
| 47 | 110 | $app->addCustomAction('stage_deleted', 'BoardHandler@boardStageDeleted', 10, 2); |
| 48 | -$app->addCustomAction('board_stage_archived', 'BoardHandler@boardStageArchived', 10, 2); | |
| 111 | +$app->addCustomAction('stage_archived', 'BoardHandler@boardStageArchived', 10, 2); | |
| 49 | 112 | $app->addCustomAction('board_stage_restored', 'BoardHandler@boardArchivedStageRestore', 10, 2); |
| 50 | 113 | $app->addCustomAction('task_created', 'BoardHandler@taskCreatedOnBoard', 10, 1); |
| 51 | 114 | $app->addCustomAction('before_task_deleted', 'BoardHandler@beforeTaskDeleted', 10, 2); |
| 52 | 115 | $app->addCustomAction('task_deleted', 'BoardHandler@taskdeleted', 10, 1); |
| 53 | 116 | $app->addCustomAction('task_moved_from_board', 'BoardHandler@taskMovedFromBoard', 10, 3); |
| 54 | -$app->addCustomAction('board_task_archived', 'BoardHandler@taskarchivedOnBoard', 10, 1); | |
| 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); | |
| 55 | 120 | $app->addCustomAction('board_member_added', 'BoardHandler@boardMemberAdded', 10, 2); |
| 121 | +$app->addCustomAction('board_viewer_added', 'BoardHandler@boardViewerAdded', 10, 2); | |
| 56 | 122 | $app->addCustomAction('board_member_removed', 'BoardHandler@boardMemberRemoved', 10, 2); |
| 57 | 123 | $app->addCustomAction('board_admin_added', 'BoardHandler@boardAdminAdded', 10, 2); |
| 58 | 124 | $app->addCustomAction('board_admin_removed', 'BoardHandler@boardAdminRemoved', 10, 2); |
| 59 | 125 | $app->addCustomAction('associate_user_add_change_remove_activity', 'ActivityHandler@associateUserAddChangeRemoveActivity', 10, 3); |
| @@ -60,25 +126,28 @@ | ||
| 60 | 126 | $app->addCustomAction('task_assignee_leave', 'ActivityHandler@taskAssigneeLeave', 10, 1); |
| 61 | 127 | $app->addCustomAction('task_assignee_join', 'ActivityHandler@taskAssigneeJoin', 10, 1); |
| 62 | 128 | $app->addCustomAction('task_label', 'ActivityHandler@taskLabelActivity', 10, 3); |
| 63 | 129 | $app->addCustomAction('label_manage_for_task_activity', 'ActivityHandler@labelManageForTaskActivity', 10, 2); |
| 64 | -$app->addCustomAction('board_label', 'BoardHandler@boardLabelActivity', 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); | |
| 65 | 133 | $app->addCustomAction('board_stages_reordered', 'BoardHandler@boardStagesReOrdered', 10, 2); |
| 134 | +$app->addCustomAction('default_assignees_updated', 'BoardHandler@defaultAssigneesUpdated', 10, 2); | |
| 66 | 135 | |
| 67 | - | |
| 68 | 136 | $app->addCustomAction('comment_created', 'NotificationHandler@addCommentNotification', 10, 1); |
| 69 | 137 | $app->addCustomAction('mention_comment_notification', 'NotificationHandler@mentionInCommentNotification', 10, 2); |
| 70 | 138 | $app->addCustomAction('subtask_added', 'NotificationHandler@addSubtaskNotification', 10, 2); |
| 71 | -$app->addCustomAction('task_date_changed', 'NotificationHandler@changeDateNotification', 10, 2); | |
| 72 | -$app->addCustomAction('task_moved_to_new_stage', 'NotificationHandler@changeStageNotification', 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); | |
| 73 | 142 | $app->addCustomAction('task_priority_changed', 'NotificationHandler@changePriorityNotification', 10, 2); |
| 74 | -$app->addCustomAction('board_task_archived', 'NotificationHandler@taskArchiveNotification', 10, 1); | |
| 75 | -$app->addCustomAction('task_content_updated', 'NotificationHandler@changeTitleOrDescriptionNotification', 10, 3); | |
| 143 | +$app->addCustomAction('task_archived', 'NotificationHandler@taskArchiveNotification', 10, 1); | |
| 144 | +$app->addCustomAction('task_content_updated', 'NotificationHandler@changeTitleNotification', 10, 3); | |
| 76 | 145 | //$app->addCustomAction('description_changed_notification', 'NotificationHandler@changeDescriptionNotification', 10, 1); |
| 77 | 146 | $app->addCustomAction('board_changed_notification', 'NotificationHandler@changeBoardNotification', 10, 2); |
| 78 | -$app->addCustomAction('task_assignee_changed', 'NotificationHandler@assigneeAddedNotification', 10, 3); | |
| 147 | +$app->addCustomAction('task_assignee_added', 'NotificationHandler@assigneeAddedNotification', 10, 2); | |
| 148 | +$app->addCustomAction('task_assignee_removed', 'NotificationHandler@assigneeRemovedNotification', 10, 2); | |
| 79 | 149 | |
| 80 | - | |
| 81 | 150 | $app->addCustomAction('one_time_schedule_send_email_for_comment', 'ScheduleHandler@sendEmailForComment', 10, 3); |
| 82 | 151 | $app->addCustomAction('one_time_schedule_send_email_for_mention', 'ScheduleHandler@sendEmailForMention', 10, 3); |
| 83 | 152 | $app->addCustomAction('one_time_schedule_send_email_for_add_assignee', 'ScheduleHandler@sendEmailForAddAssignee', 10, 3); |
| 84 | 153 | $app->addCustomAction('one_time_schedule_send_email_for_remove_assignee', 'ScheduleHandler@sendEmailForRemoveAssignee', 10, 3); |
| @@ -90,18 +159,20 @@ | ||
| 90 | 159 | $app->addCustomAction('one_time_schedule_send_email_for_removed_from_task', 'ScheduleHandler@sendEmailForChangeStage', 10, 3); |
| 91 | 160 | |
| 92 | 161 | $app->addCustomAction('task_attachment_added', 'ActivityHandler@taskAttachmentAdded', 10, 1); |
| 93 | 162 | $app->addCustomAction('task_attachment_deleted', 'ActivityHandler@taskAttachmentDeleted', 10, 1); |
| 94 | -$app->addCustomAction('task_attachment_deleted', 'TaskHandler@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 | |
| 95 | 165 | $app->addAction('deleted_user', 'BoardHandler@deleteUserRelatedData', 10, 3); |
| 96 | -$app->addAction('delete_attachment', 'FileHandler@mediaFileDeleted', 10, 1); | |
| 166 | +$app->addAction('fluent_boards/delete_remote_attachment_files', 'FileHandler@deleteRemoteAttachmentFiles', 10, 1); | |
| 97 | 167 | |
| 98 | -$app->addCustomAction('send_invitation', 'ScheduleHandler@sendInvitationViaEmail', 10, 3); | |
| 99 | 168 | $app->addCustomAction('task_created', 'TaskHandler@onTaskCreated', 10, 1); |
| 100 | 169 | $app->addCustomAction('comment_created', 'TaskHandler@onCommentCreated', 10, 1); |
| 101 | 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); | |
| 102 | 174 | |
| 103 | - | |
| 104 | 175 | if (defined('WP_CLI') && WP_CLI) { |
| 105 | 176 | \WP_CLI::add_command('fluent_boards', '\FluentBoards\App\Hooks\Cli\Commands'); |
| 106 | 177 | } |
| 107 | 178 | |
| @@ -106,22 +177,24 @@ | ||
| 106 | 177 | } |
| 107 | 178 | |
| 108 | 179 | |
| 109 | 180 | add_action('init', function () { |
| 181 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public routing parameter check, no data modification | |
| 110 | 182 | if (!isset($_REQUEST['fbs'])) { |
| 111 | 183 | return; |
| 112 | 184 | } |
| 113 | 185 | }); |
| 114 | 186 | |
| 115 | - | |
| 116 | 187 | /* |
| 117 | 188 | * IMPORTANT |
| 118 | 189 | * External Pages Handler |
| 119 | 190 | * Each Request must have fbs=1 as a query parameter, then the plugin will handle the request. |
| 120 | 191 | */ |
| 192 | +// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public routing logic, security validated in individual handlers | |
| 121 | 193 | if(isset($_GET['fbs']) && $_GET['fbs'] == 1) { |
| 122 | 194 | |
| 123 | 195 | // For viewing attachment |
| 196 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public routing logic, security validated in handler | |
| 124 | 197 | if(isset($_GET['route']) && ($_GET['route'] == 'task')) { |
| 125 | 198 | add_action('init', function() { |
| 126 | 199 | (new \FluentBoardsPro\App\Hooks\Handlers\ExternalPages())->handleTaskWebhook(); |
| 127 | 200 | }); |
| @@ -126,10 +199,12 @@ | ||
| 126 | 199 | (new \FluentBoardsPro\App\Hooks\Handlers\ExternalPages())->handleTaskWebhook(); |
| 127 | 200 | }); |
| 128 | 201 | } |
| 129 | 202 | |
| 203 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public file serving endpoint, security validated by hash in handler | |
| 130 | 204 | if(isset($_GET['fbs_comment_image'])) { |
| 131 | - if(isset($_GET['fbs_type']) == 'public_url') { | |
| 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') { | |
| 132 | 207 | add_action('init', function() { |
| 133 | 208 | (new \FluentBoards\App\Hooks\Handlers\ExternalPages())->view_uploaded_comment_image(); |
| 134 | 209 | }); |
| 135 | 210 | } else { |
| @@ -139,5 +214,68 @@ | ||
| 139 | 214 | } |
| 140 | 215 | |
| 141 | 216 | } |
| 142 | 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); | |
| 143 | 281 | } |