← All changes
|
app/Services/Intergrations/FluentCRM/DeepIntegration.php
+180
-2
1.21
→
2.2.0
View file →
| @@ -1,16 +1,26 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace FluentBoards\App\Services\Intergrations\FluentCRM; |
| 4 | 4 | |
| 5 | - | |
| 6 | 5 | use FluentBoards\App\Models\Board; |
| 6 | +use FluentBoards\App\Models\Task; | |
| 7 | +use FluentBoards\App\Models\TaskMeta; | |
| 8 | +use FluentBoards\App\Models\User; | |
| 9 | +use FluentBoards\App\Services\PermissionManager; | |
| 10 | +use FluentBoards\Framework\Database\Orm\Builder; | |
| 7 | 11 | |
| 8 | 12 | class DeepIntegration |
| 9 | 13 | { |
| 14 | + private const USER_TEMPLATE_OPTION_LIMIT = 50; | |
| 15 | + | |
| 10 | 16 | public function init() |
| 11 | 17 | { |
| 12 | 18 | add_filter('fluentcrm_ajax_options_boards', [$this, 'getBoards'], 10, 3); |
| 19 | + add_filter('fluentcrm_ajax_options_task_templates', [$this, 'getTaskTemplates'], 10, 3); | |
| 20 | + add_filter('fluentcrm_ajax_options_board_default_templates', [$this, 'getDefaultBoardTemplates'], 10, 3); | |
| 21 | + add_filter('fluentcrm_ajax_options_board_user_templates', [$this, 'getUserBoardTemplates'], 10, 3); | |
| 22 | + add_filter('fluentcrm_ajax_options_board_members', [$this, 'getBoardMembers'], 10, 3); | |
| 13 | 23 | } |
| 14 | 24 | |
| 15 | 25 | public function getBoards($records, $search, $includeIds) |
| 16 | 26 | { |
| @@ -35,5 +45,173 @@ | ||
| 35 | 45 | ]; |
| 36 | 46 | } |
| 37 | 47 | return $formattedBoards; |
| 38 | 48 | } |
| 39 | -} | |
| 49 | + | |
| 50 | + public function getTaskTemplates($records, $search, $includeIds) | |
| 51 | + { | |
| 52 | + if (!defined('FLUENT_BOARDS_PRO')) { | |
| 53 | + return []; | |
| 54 | + } | |
| 55 | + | |
| 56 | + $userId = get_current_user_id(); | |
| 57 | + $currentUser = User::find($userId); | |
| 58 | + | |
| 59 | + $relatedBoardsQuery = Board::query(); | |
| 60 | + | |
| 61 | + if (!PermissionManager::isAdmin($userId)) { | |
| 62 | + $relatedBoardsQuery->whereIn('id', $currentUser->whichBoards->pluck('id')); | |
| 63 | + } | |
| 64 | + | |
| 65 | + $templateTaskIds = TaskMeta::where('key', 'is_template')->where('value', 'yes')->pluck('task_id'); | |
| 66 | + $query = Task::whereIn('id', $templateTaskIds) | |
| 67 | + ->where('archived_at', null) | |
| 68 | + ->whereIn('board_id', $relatedBoardsQuery->pluck('id')) | |
| 69 | + ->with('assignees', 'labels'); | |
| 70 | + | |
| 71 | + | |
| 72 | + if (!empty($search)) { | |
| 73 | + $query->where('title', 'like', "%$search%"); | |
| 74 | + } | |
| 75 | + | |
| 76 | + $templateTasks = $query->orderBy('title', 'ASC')->get(); | |
| 77 | + | |
| 78 | + $formattedTemplateTasks = []; | |
| 79 | + foreach ($templateTasks as $task) { | |
| 80 | + $formattedTemplateTasks[] = [ | |
| 81 | + 'id' => strval($task->id), | |
| 82 | + 'title' => $task->title | |
| 83 | + ]; | |
| 84 | + } | |
| 85 | + return $formattedTemplateTasks; | |
| 86 | + } | |
| 87 | + | |
| 88 | + public function getDefaultBoardTemplates($records, $search, $includeIds) | |
| 89 | + { | |
| 90 | + // Built-in board templates are coming soon. Keep this selector empty so | |
| 91 | + // FluentCRM option lookups do not request the external default-template API. | |
| 92 | + return []; | |
| 93 | + } | |
| 94 | + | |
| 95 | + /** | |
| 96 | + * Return bounded user-template options for the FluentCRM selector. | |
| 97 | + * | |
| 98 | + * @param array $records | |
| 99 | + * @param string $search | |
| 100 | + * @param array $includeIds | |
| 101 | + * @return array | |
| 102 | + */ | |
| 103 | + public function getUserBoardTemplates($records, $search, $includeIds) | |
| 104 | + { | |
| 105 | + if (!defined('FLUENT_BOARDS_PRO')) { | |
| 106 | + return []; | |
| 107 | + } | |
| 108 | + | |
| 109 | + global $wpdb; | |
| 110 | + | |
| 111 | + $search = is_scalar($search) ? sanitize_text_field((string) $search) : ''; | |
| 112 | + $includeIds = array_slice(array_values(array_unique(array_filter( | |
| 113 | + array_map('absint', (array) $includeIds) | |
| 114 | + ))), 0, self::USER_TEMPLATE_OPTION_LIMIT); | |
| 115 | + | |
| 116 | + $query = $this->newUserTemplateOptionsQuery(); | |
| 117 | + | |
| 118 | + if ($search !== '') { | |
| 119 | + $query->where('title', 'like', '%' . $wpdb->esc_like($search) . '%'); | |
| 120 | + } | |
| 121 | + | |
| 122 | + $templates = $query->orderBy('title', 'ASC') | |
| 123 | + ->limit(self::USER_TEMPLATE_OPTION_LIMIT) | |
| 124 | + ->get(); | |
| 125 | + $options = $this->formatUserTemplateOptions($templates); | |
| 126 | + | |
| 127 | + // Preserve saved selections even when they fall outside the current result page. | |
| 128 | + $missingIds = array_diff($includeIds, array_keys($options)); | |
| 129 | + if ($missingIds) { | |
| 130 | + $selectedTemplates = $this->newUserTemplateOptionsQuery() | |
| 131 | + ->whereIn('id', $missingIds) | |
| 132 | + ->get(); | |
| 133 | + $options += $this->formatUserTemplateOptions($selectedTemplates); | |
| 134 | + } | |
| 135 | + | |
| 136 | + $selectedOptions = array_intersect_key($options, array_flip($includeIds)); | |
| 137 | + $regularOptions = array_diff_key($options, $selectedOptions); | |
| 138 | + $options = array_merge( | |
| 139 | + array_values($selectedOptions), | |
| 140 | + array_slice( | |
| 141 | + array_values($regularOptions), | |
| 142 | + 0, | |
| 143 | + max(0, self::USER_TEMPLATE_OPTION_LIMIT - count($selectedOptions)) | |
| 144 | + ) | |
| 145 | + ); | |
| 146 | + | |
| 147 | + usort($options, function ($first, $second) { | |
| 148 | + return strcasecmp($first['title'], $second['title']); | |
| 149 | + }); | |
| 150 | + | |
| 151 | + return $options; | |
| 152 | + } | |
| 153 | + | |
| 154 | + /** | |
| 155 | + * Create a fresh query for lightweight user-template selector options. | |
| 156 | + * | |
| 157 | + * @return Builder | |
| 158 | + */ | |
| 159 | + private function newUserTemplateOptionsQuery() | |
| 160 | + { | |
| 161 | + return Board::select(['id', 'title']) | |
| 162 | + ->whereIn('type', ['to-do', 'roadmap']) | |
| 163 | + ->onlyTemplates(); | |
| 164 | + } | |
| 165 | + | |
| 166 | + /** | |
| 167 | + * Format template models as selector options keyed by integer ID. | |
| 168 | + * | |
| 169 | + * @param iterable $templates | |
| 170 | + * @return array | |
| 171 | + */ | |
| 172 | + private function formatUserTemplateOptions($templates) | |
| 173 | + { | |
| 174 | + $options = []; | |
| 175 | + | |
| 176 | + foreach ($templates as $template) { | |
| 177 | + $options[(int) $template->id] = [ | |
| 178 | + 'id' => (int) $template->id, | |
| 179 | + 'title' => $template->title, | |
| 180 | + ]; | |
| 181 | + } | |
| 182 | + | |
| 183 | + return $options; | |
| 184 | + } | |
| 185 | + | |
| 186 | + public function getBoardMembers($records, $search, $includeIds) | |
| 187 | + { | |
| 188 | + // Get all WordPress users | |
| 189 | + $args = [ | |
| 190 | + 'orderby' => 'display_name', | |
| 191 | + 'order' => 'ASC', | |
| 192 | + ]; | |
| 193 | + | |
| 194 | + // Add search filter if provided | |
| 195 | + if (!empty($search)) { | |
| 196 | + $args['search'] = '*' . $search . '*'; | |
| 197 | + $args['search_columns'] = ['user_login', 'user_email', 'display_name']; | |
| 198 | + } | |
| 199 | + | |
| 200 | + // Include specific IDs if provided | |
| 201 | + if (!empty($includeIds)) { | |
| 202 | + $args['include'] = $includeIds; | |
| 203 | + } | |
| 204 | + | |
| 205 | + $users = get_users($args); | |
| 206 | + | |
| 207 | + $formattedMembers = []; | |
| 208 | + foreach ($users as $user) { | |
| 209 | + $formattedMembers[] = [ | |
| 210 | + 'id' => strval($user->ID), | |
| 211 | + 'title' => $user->display_name . ' (' . $user->user_email . ')' | |
| 212 | + ]; | |
| 213 | + } | |
| 214 | + | |
| 215 | + return $formattedMembers; | |
| 216 | + } | |
| 217 | +} | |