PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
← All changes | app/Http/Controllers/SpaceController.php +463 -166 1.0.952.11.0 View file →
@@ -1,9 +1,8 @@
1 1 <?php
2 2
3 3 namespace FluentCommunity\App\Http\Controllers;
4 4
5 -use FluentCommunity\App\Functions\Utility;
6 5 use FluentCommunity\App\Models\Space;
7 6 use FluentCommunity\App\Models\Feed;
8 7 use FluentCommunity\App\Models\BaseSpace;
9 8 use FluentCommunity\App\Models\SpaceGroup;
@@ -8,11 +7,11 @@
8 7 use FluentCommunity\App\Models\BaseSpace;
9 8 use FluentCommunity\App\Models\SpaceGroup;
10 9 use FluentCommunity\App\Models\User;
11 10 use FluentCommunity\App\Services\CustomSanitizer;
12 -use FluentCommunity\App\Services\FeedsHelper;
13 11 use FluentCommunity\App\Services\Helper;
14 -use FluentCommunity\App\Services\LockscreenService;
12 +use FluentCommunity\App\Services\SpaceMenuService;
13 +use FluentCommunity\App\Functions\Utility;
15 14 use FluentCommunity\App\Services\ProfileHelper;
16 15 use FluentCommunity\Framework\Http\Request\Request;
17 16 use FluentCommunity\App\Models\Comment;
18 17 use FluentCommunity\App\Models\Reaction;
@@ -28,59 +27,85 @@
28 27 $q->where('user_id', get_current_user_id());
29 28 })
30 29 ->get();
31 30
32 - return [
31 + $data = [
33 32 'spaces' => $spaces
34 33 ];
34 +
35 + return apply_filters('fluent_community/spaces_api_response', $data, $this->request->all());
35 36 }
36 37
37 38 public function create(Request $request)
38 39 {
39 40 $currentUser = $this->getUser(true);
40 - $currentUser->verifyCommunityPermission('community_admin');
41 41
42 42 $data = $request->get('space', []);
43 43
44 - if (empty($data['slug'])) {
45 - $data['slug'] = sanitize_title($data['title'], '');
46 - } else {
47 - $data['slug'] = sanitize_title($data['slug'], '');
48 - }
44 + $slug = Arr::get($data, 'slug') ?: Arr::get($data, 'title');
49 45
50 - $data['title'] = sanitize_text_field($data['title']);
51 - $data['privacy'] = sanitize_text_field($data['privacy']);
46 + $slug = preg_replace('/[^a-zA-Z0-9-_]/', '', $slug);
52 47
48 + $data['slug'] = sanitize_title($slug, '');
49 + $data['title'] = sanitize_text_field(Arr::get($data, 'title'));
50 + $data['privacy'] = sanitize_text_field(Arr::get($data, 'privacy'));
51 +
53 52 $this->validate($data, [
54 - 'title' => 'required',
55 - 'slug' => 'required|unique:fcom_spaces,slug',
56 - 'privacy' => 'required|in:public,private,secret',
57 - 'parent_id' => 'required|exists:fcom_spaces,id'
58 - ], [
59 - 'parent_id.required' => __('Please select a menu group', 'fluent-community')
53 + 'title' => 'required',
54 + 'slug' => 'unique:fcom_spaces,slug',
55 + 'privacy' => 'required|in:public,private,secret'
60 56 ]);
61 57
62 - $spaceGroup = SpaceGroup::findOrFail($data['parent_id']);
58 + if (Arr::get($data, 'settings.topic_required') === 'yes' && !array_filter((array)Arr::get($data, 'topic_ids', []))) {
59 + return $this->sendError([
60 + 'message' => __('Please select at least one topic when members are required to select a topic.', 'fluent-community')
61 + ]);
62 + }
63 63
64 + $spaceGroup = null;
65 + if (!empty($data['parent_id'])) {
66 + $spaceGroup = SpaceGroup::findOrFail($data['parent_id']);
67 + $serial = BaseSpace::query()->withoutGlobalScopes()->where('parent_id', $spaceGroup->id)->max('serial') + 1;
68 + } else {
69 + $serial = BaseSpace::query()->withoutGlobalScopes()->max('serial') + 1;
70 + }
71 +
72 + $settings = CustomSanitizer::santizeSpaceSettings(Arr::get($data, 'settings', []), $data['privacy']);
73 +
74 + if (is_wp_error($settings)) {
75 + return $this->sendError([
76 + 'message' => $settings->get_error_message()
77 + ]);
78 + }
79 +
64 80 $spaceData = apply_filters('fluent_community/space/create_data', [
65 81 'title' => sanitize_text_field($data['title']),
66 82 'slug' => $data['slug'],
67 83 'privacy' => $data['privacy'],
68 - 'description' => sanitize_textarea_field($data['description']),
69 - 'settings' => [
70 - 'restricted_post_only' => Arr::get($data, 'settings.restricted_post_only', 'no'),
71 - 'emoji' => CustomSanitizer::sanitizeEmoji(Arr::get($data, 'settings.emoji', '')),
72 - 'can_request_join' => Arr::get($data, 'settings.can_request_join', 'no'),
73 - 'custom_lock_screen' => Arr::get($data, 'settings.custom_lock_screen', 'no'),
74 - 'layout_style' => Arr::get($data, 'settings.layout_style', 'timeline'),
75 - 'show_sidebar' => Arr::get($data, 'settings.show_sidebar', 'yes'),
76 - 'shape_svg' => CustomSanitizer::sanitizeSvg(Arr::get($data, 'settings.shape_svg', '')),
77 - ],
78 - 'parent_id' => $spaceGroup->id,
79 - 'serial' => BaseSpace::where('parent_id', $spaceGroup->id)->max('serial') + 1
84 + 'description' => sanitize_textarea_field(Arr::get($data, 'description', '')),
85 + 'settings' => $settings,
86 + 'parent_id' => $spaceGroup ? $spaceGroup->id : null,
87 + 'serial' => $serial ?: 1
80 88 ]);
81 89
90 + $ogImage = Arr::get($data, 'settings.og_image', '');
91 + $ogMedia = null;
92 + if ($ogImage) {
93 + $ogMedia = Helper::getMediaFromUrl($ogImage);
94 + if ($ogMedia && !$ogMedia->is_active) {
95 + $spaceData['settings']['og_image'] = $ogMedia->public_url;
96 + }
97 + }
98 +
82 99 $space = Space::create($spaceData);
100 + if ($ogMedia && !$ogMedia->is_active) {
101 + $ogMedia->update([
102 + 'is_active' => true,
103 + 'user_id' => get_current_user_id(),
104 + 'sub_object_id' => $space->id,
105 + 'object_source' => 'space_og_image'
106 + ]);
107 + }
83 108
84 109 $imageTypes = ['cover_photo', 'logo'];
85 110 $metaData = [];
86 111 foreach ($imageTypes as $type) {
@@ -106,8 +131,15 @@
106 131 $space->members()->attach(get_current_user_id(), [
107 132 'role' => 'admin'
108 133 ]);
109 134
135 + if (Arr::has($data, 'topic_ids')) {
136 + $topicsConfig = Helper::getTopicsConfig();
137 + $topicIds = (array)Arr::get($data, 'topic_ids', []);
138 + $topicIds = array_slice($topicIds, 0, $topicsConfig['max_topics_per_space']);
139 + $space->syncTopics($topicIds);
140 + }
141 +
110 142 $currentUser->cacheAccessSpaces();
111 143 do_action('fluent_community/space/created', $space, $data);
112 144
113 145 return [
@@ -117,56 +149,130 @@
117 149 }
118 150
119 151 public function discover(Request $request)
120 152 {
121 - $spaces = Space::orderBy('title', 'ASC')
122 - ->with(['space_pivot' => function ($q) {
123 - $q->where('user_id', get_current_user_id());
124 - }])
153 + $start = microtime(true);
154 + $currentUser = $this->getUser();
155 + $type = $request->getSafe('type');
156 + $search = $request->getSafe('search');
157 + $sortBy = $request->getSafe('sort_by', 'sanitize_text_field', 'alphabetical');
158 +
159 + $spaces = Space::with(['space_pivot' => function ($q) {
160 + $q->where('user_id', get_current_user_id());
161 + }])
125 162 ->where(function ($q) {
126 163 $q->whereHas('space_pivot', function ($q) {
164 + $q->where('user_id', get_current_user_id())
165 + ->where('status', 'active');
166 + })
167 + ->orWhereIn('privacy', ['public', 'private']);
168 + })
169 + ->when($type == 'joined', function ($q) {
170 + $q->whereHas('space_pivot', function ($q) {
127 171 $q->where('user_id', get_current_user_id());
172 + });
173 + })
174 + ->when($search, function ($q) use ($search) {
175 + $q->where('title', 'like', '%' . $search . '%');
176 + })
177 + ->when($sortBy == 'alphabetical', function ($q) {
178 + $q->orderBy('title', 'ASC');
179 + })
180 + ->when($sortBy == 'oldest', function ($q) {
181 + $q->orderBy('created_at', 'ASC');
182 + })
183 + ->when($sortBy == 'latest', function ($q) {
184 + $q->orderBy('created_at', 'DESC');
185 + })
186 + ->paginate();
187 +
188 + $memberCounts = $this->getActiveMemberCounts($spaces->pluck('id')->toArray());
189 +
190 + foreach ($spaces as $space) {
191 + $space->description_rendered = wpautop($space->description);
192 +
193 + $shouldHideMembersCount = Arr::get($space->settings, 'hide_members_count') == 'yes';
194 + $canViewMembers = $currentUser && $space->verifyUserPermisson($currentUser, 'can_view_members', false);
195 +
196 + if ($shouldHideMembersCount && !$canViewMembers) {
197 + $space->members_count = 0;
198 + continue;
199 + }
200 +
201 + $space->members_count = (int)Arr::get($memberCounts, $space->id, 0);
202 + }
203 +
204 + $data = [
205 + 'spaces' => $spaces,
206 + 'execution_time' => microtime(true) - $start
207 + ];
208 +
209 + return apply_filters('fluent_community/spaces_api_response', $data, $request->all());
210 + }
211 +
212 + public function getAllSpaces(Request $request)
213 + {
214 + $currentUser = $this->getUser();
215 +
216 + $spacesQuery = Space::query();
217 +
218 + if (!($currentUser && $currentUser->isCommunityModerator())) {
219 + $spacesQuery->where(function ($q) {
220 + $q->whereHas('space_pivot', function ($q) {
221 + $q->where('user_id', get_current_user_id())
222 + ->where('status', 'active');
128 223 })
129 224 ->orWhereIn('privacy', ['public', 'private']);
130 - })
131 - ->get();
225 + });
226 + }
227 + $spaces = $spacesQuery->paginate();
132 228
229 + $memberCounts = $this->getActiveMemberCounts($spaces->pluck('id')->toArray());
230 +
133 231 foreach ($spaces as $space) {
134 - $space->members_count = $space->members()->wherePivot('status', 'active')->count();
232 + $shouldHideMembersCount = Arr::get($space->settings, 'hide_members_count') == 'yes';
233 + $canViewMembers = $currentUser && $space->verifyUserPermisson($currentUser, 'can_view_members', false);
234 +
235 + if ($shouldHideMembersCount && !$canViewMembers) {
236 + $space->members_count = 0;
237 + continue;
238 + }
239 +
240 + $space->members_count = (int)Arr::get($memberCounts, $space->id, 0);
241 +
242 + $space->formatSpaceData($currentUser);
243 + do_action_ref_array('fluent_community/space', [&$space]);
135 244 }
136 245
137 - return [
246 + return apply_filters('fluent_community/all_spaces_api_response', [
138 247 'spaces' => $spaces
139 - ];
248 + ], $request->all());
140 249 }
141 250
142 251 public function getBySlug(Request $request, $spaceSlug)
143 252 {
144 253 $user = $this->getUser();
145 - $space = Space::where('slug', $spaceSlug)
146 - ->firstOrFail();
254 + $space = Space::where('slug', $spaceSlug)->first();
147 255
148 - $space->permissions = $space->getUserPermissions($user);
149 - $space->description_rendered = FeedsHelper::mdToHtml($space->description);
150 - $space->membership = $space->getMembership(get_current_user_id());
151 - $space->topics = Utility::getTopicsBySpaceId($space->id);
256 + $userId = $user ? $user->ID : null;
152 257
153 - if (!Helper::isSiteAdmin()) {
154 - $space->lockscreen_config = LockscreenService::getLockscreenConfig($space, $space->membership);
258 + // A hidden secret space must be indistinguishable from a non-existent one, so its
259 + // existence cannot be enumerated by slug. Both return an identical 404.
260 + if (!$space || ($space->privacy == 'secret' && !$space->getMembership($userId) && !$space->isAdmin($userId, true))) {
261 + return $this->sendError([
262 + 'message' => __('Space not found', 'fluent-community')
263 + ], 404);
155 264 }
156 265
157 - if ($space->privacy == 'secret' && !$space->membership) {
158 - return $this->sendError([
159 - 'message' => __('You are not allowed to view this space', 'fluent-community'),
160 - 'error_type' => 'restricted'
161 - ]);
162 - }
266 + $space = $space->formatSpaceData($user);
163 267
164 268 do_action_ref_array('fluent_community/space', [&$space]);
165 269
166 - return [
270 + $data = [
167 271 'space' => $space
168 272 ];
273 +
274 + return apply_filters('fluent_community/space_api_response', $data, $request->all());
169 275 }
170 276
171 277 public function patchBySlug(Request $request, $slug)
172 278 {
@@ -174,42 +280,57 @@
174 280 ->first();
175 281
176 282 if (!$space) {
177 283 return $this->sendError([
178 - 'message' => 'Space not found'
284 + 'message' => __('Space not found', 'fluent-community')
179 285 ]);
180 286 }
181 287
182 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
288 + $data = $request->get('data', []);
183 289
184 - $data = $request->get('data', []);
290 + if (Arr::has($data, 'title') && !trim(sanitize_text_field(Arr::get($data, 'title', '')))) {
291 + return $this->sendError([
292 + 'message' => __('Space title is required.', 'fluent-community')
293 + ]);
294 + }
185 295
186 - if (!empty($data['title'])) {
187 - $taken = Space::where('title', $data['title'])
296 + if (!empty($data['slug'])) {
297 + $taken = Space::where('slug', $data['slug'])
188 298 ->where('id', '!=', $space->id)
189 299 ->first();
300 +
190 301 if ($taken) {
191 302 return $this->sendError([
192 - 'message' => 'Space title is already taken. Please use a different title'
303 + 'message' => __('Slug is already taken. Please use a different slug', 'fluent-community')
193 304 ]);
194 305 }
195 306 }
196 307
308 + $topicRequired = Arr::has($data, 'settings.topic_required')
309 + ? Arr::get($data, 'settings.topic_required')
310 + : Arr::get($space->settings, 'topic_required');
311 +
312 + if ($topicRequired === 'yes') {
313 + $topicIds = Arr::has($data, 'topic_ids')
314 + ? (array)Arr::get($data, 'topic_ids', [])
315 + : array_column(Utility::getTopicsBySpaceId($space->id), 'id');
316 +
317 + if (!array_filter($topicIds)) {
318 + return $this->sendError([
319 + 'message' => __('Please select at least one topic when members are required to select a topic.', 'fluent-community')
320 + ]);
321 + }
322 + }
323 +
197 324 $mediaTypes = ['cover_photo', 'logo'];
198 325 foreach ($mediaTypes as $type) {
199 326 if (!empty($data[$type])) {
200 327 $media = Helper::getMediaFromUrl($data[$type]);
201 - if (!$media) {
328 + if (!$media || $media->is_active) {
202 329 unset($data[$type]);
203 330 continue;
204 331 }
205 332
206 - if (!$media || $media->is_active) {
207 - return $this->sendError([
208 - 'message' => 'Invalid media image. Please upload a new one.'
209 - ]);
210 - }
211 -
212 333 $data[$type] = $media->public_url;
213 334
214 335 $media->update([
215 336 'is_active' => true,
@@ -223,22 +344,53 @@
223 344 }
224 345
225 346 $data = apply_filters('fluent_community/space/update_data', $data, $space);
226 347
227 - $space->updateCustomData($data, true);
348 + if (isset($data['parent_id']) && !$data['parent_id']) {
349 + $data['parent_id'] = '';
350 + }
228 351
352 + $space = $space->updateCustomData($data, true);
353 +
354 + if (is_wp_error($space)) {
355 + return $this->sendError([
356 + 'message' => $space->get_error_message()
357 + ]);
358 + }
359 +
229 360 if (Arr::has($data, 'topic_ids')) {
361 + $topicsConfig = Helper::getTopicsConfig();
230 362 $topicIds = (array)Arr::get($data, 'topic_ids', []);
363 + $topicIds = array_slice($topicIds, 0, $topicsConfig['max_topics_per_space']);
231 364 $space->syncTopics($topicIds);
232 365 }
233 366
367 + do_action('fluent_community/space/updated', $space, $data);
368 + $slugUpdated = $slug != $space->slug;
369 +
370 + $metaSettings = $request->get('meta_settings', []);
371 + if($metaSettings) {
372 + foreach ($metaSettings as $metaProvider => $metaData) {
373 + do_action('fluent_community/space/update_meta_settings_'.$metaProvider, $metaData, $space);
374 + }
375 + }
376 +
234 377 return [
235 - 'message' => __('Space has been updated', 'fluent-community')
378 + 'message' => __('Settings have been updated', 'fluent-community'),
379 + 'redirect_url' => $slugUpdated ? $space->getPermalink() : ''
236 380 ];
237 381 }
238 382
383 + public function patchById(Request $request, $id)
384 + {
385 + $space = Space::findOrFail($id);
386 +
387 + return $this->patchBySlug($request, $space->slug);
388 + }
389 +
239 390 public function getMembers(Request $request, $slug)
240 391 {
392 + /** @var Space $space */
241 393 $space = Space::where('slug', $slug)
242 394 ->firstOrFail();
243 395
244 396 $user = $this->getUser();
@@ -248,8 +400,9 @@
248 400 'message' => __('You are not allowed to view members of this space', 'fluent-community'),
249 401 'permission_failed' => true
250 402 ]);
251 403 }
404 +
252 405 $search = $request->getSafe('search', 'sanitize_text_field');
253 406
254 407 $pendingCount = 0;
255 408 if ($user && $user->can('can_add_member', $space)) {
@@ -268,30 +421,48 @@
268 421 }])
269 422 ->where('status', 'pending')
270 423 ->paginate();
271 424
272 - return [
425 + return apply_filters('fluent_community/space_members_api_response', [
273 426 'members' => $pendingRequests,
274 427 'pending_count' => $pendingCount
275 - ];
428 + ], $pendingRequests, $request->all());
276 429 }
277 430 }
278 431
432 + $defaultDirections = [
433 + 'last_activity' => 'DESC',
434 + 'display_name' => 'ASC',
435 + 'created_at' => 'DESC',
436 + ];
437 +
438 + $sortBy = $request->getSafe('sort_by', 'sanitize_text_field', 'created_at');
439 + $sortColumn = in_array($sortBy, array_keys($defaultDirections), true) ? $sortBy : 'created_at';
440 + $sortDir = strtoupper($request->getSafe('sort_dir', 'sanitize_text_field', ''));
441 + $sortDirection = in_array($sortDir, ['ASC', 'DESC'], true) ? $sortDir : ($sortColumn === 'created_at' ? 'ASC' : $defaultDirections[$sortColumn]);
442 +
443 + $profileSort = $sortColumn !== 'created_at';
444 + $orderColumn = $profileSort ? 'fcom_xprofile.' . $sortColumn : 'fcom_space_user.created_at';
445 +
279 446 $spaceMembers = SpaceUserPivot::bySpace($space->id)
280 447 ->whereHas('xprofile', function ($q) use ($search) {
281 - return $q->searchBy($search)
282 - ->where('status', 'active');
448 + $q->searchBy($search)->where('status', 'active');
283 449 })
450 + ->where('fcom_space_user.status', 'active')
284 451 ->with(['xprofile' => function ($q) {
285 452 $q->select(ProfileHelper::getXProfilePublicFields());
286 453 }])
287 - ->where('status', 'active')
454 + ->when($profileSort, function ($q) {
455 + $q->join('fcom_xprofile', 'fcom_xprofile.user_id', '=', 'fcom_space_user.user_id')
456 + ->select('fcom_space_user.*');
457 + })
458 + ->orderBy($orderColumn, $sortDirection)
288 459 ->paginate();
289 460
290 - return [
461 + return apply_filters('fluent_community/space_members_api_response', [
291 462 'members' => $spaceMembers,
292 463 'pending_count' => $pendingCount
293 - ];
464 + ], $spaceMembers, $request->all());
294 465 }
295 466
296 467 public function join(Request $request, $slug)
297 468 {
@@ -298,9 +469,9 @@
298 469 $space = Space::where('slug', $slug)->first();
299 470
300 471 if (!$space) {
301 472 return $this->sendError([
302 - 'message' => 'Space not found'
473 + 'message' => __('Space not found', 'fluent-community')
303 474 ]);
304 475 }
305 476
306 477 $user = $this->getUser();
@@ -308,22 +479,23 @@
308 479 $membership = $space->getMembership(get_current_user_id());
309 480
310 481 if ($membership) {
311 482 return $this->sendError([
312 - 'message' => 'You are already a member of this space. Please reload this page'
483 + 'message' => __('You are already a member of this space. Please reload this page', 'fluent-community')
313 484 ]);
314 485 }
315 486
316 487 $roles = $user->getCommunityRoles();
488 + $isPrivileged = !!array_intersect($roles, ['admin', 'moderator']);
317 489
318 - if (!$roles && $space->privacy == 'secret') {
490 + if (!$isPrivileged && $space->privacy == 'secret') {
319 491 return $this->sendError([
320 - 'message' => 'You are not allowed to join this space'
492 + 'message' => __('You are not allowed to join this space', 'fluent-community')
321 493 ]);
322 494 }
323 495
324 496 $status = 'active';
325 - if (!$roles) {
497 + if (!$isPrivileged) {
326 498 if ($space->privacy != 'public') {
327 499 $status = apply_filters('fluent_community/space/join_status_for_private', 'pending', $space, $user);
328 500
329 501 if (!in_array($status, ['pending', 'active'])) {
@@ -351,9 +523,9 @@
351 523
352 524 $user->cacheAccessSpaces();
353 525
354 526 return [
355 - 'message' => ($status == 'active') ? __('You have joined this Space', 'fluent-community') : __('Your join request has been sent to the Space admin', 'fluent-community'),
527 + 'message' => ($status == 'active') ? __('You have joined this Space', 'fluent-community') : __('Your join request has been sent to the Space admin.', 'fluent-community'),
356 528 'membership' => $space->membership
357 529 ];
358 530 }
359 531
@@ -363,9 +535,9 @@
363 535 $space = Space::where('slug', $slug)->first();
364 536
365 537 if (!$space) {
366 538 return $this->sendError([
367 - 'message' => 'Space not found'
539 + 'message' => __('Space not found', 'fluent-community')
368 540 ]);
369 541 }
370 542
371 543 $membership = $space->getMembership($user->ID);
@@ -371,9 +543,9 @@
371 543 $membership = $space->getMembership($user->ID);
372 544
373 545 if (!$membership) {
374 546 return $this->sendError([
375 - 'message' => 'You are not a member of this community'
547 + 'message' => __('You are not a member of this community', 'fluent-community')
376 548 ]);
377 549 }
378 550
379 551 Helper::removeFromSpace($space, $user->ID, 'self');
@@ -382,24 +554,18 @@
382 554 'message' => __('You have left this space', 'fluent-community')
383 555 ];
384 556 }
385 557
386 - public function delete(Request $request, $slug)
558 + public function deleteBySlug(Request $request, $slug)
387 559 {
388 560 $space = Space::where('slug', $slug)->first();
389 561
390 562 if (!$space) {
391 563 return $this->sendError([
392 - 'message' => 'Space not found'
564 + 'message' => __('Space not found', 'fluent-community')
393 565 ]);
394 566 }
395 567
396 - if (!Helper::isSiteAdmin()) {
397 - return $this->sendError([
398 - 'message' => 'You are not allowed to delete this community'
399 - ]);
400 - }
401 -
402 568 do_action('fluent_community/space/before_delete', $space);
403 569
404 570 Comment::whereHas('post', function ($q) use ($space) {
405 571 $q->where('space_id', $space->id);
@@ -422,8 +588,15 @@
422 588 'message' => __('Space has been deleted successfully', 'fluent-community')
423 589 ];
424 590 }
425 591
592 + public function deleteById(Request $request, $id)
593 + {
594 + $space = Space::findOrFail($id);
595 +
596 + return $this->deleteBySlug($request, $space->slug);
597 + }
598 +
426 599 public function addMember(Request $request, $slug)
427 600 {
428 601 $space = Space::where('slug', $slug)->first();
429 602
@@ -428,9 +601,9 @@
428 601 $space = Space::where('slug', $slug)->first();
429 602
430 603 if (!$space) {
431 604 return $this->sendError([
432 - 'message' => 'Space not found'
605 + 'message' => __('Space not found', 'fluent-community')
433 606 ]);
434 607 }
435 608
436 609 $this->validate($request->all(), [
@@ -438,9 +611,10 @@
438 611 ]);
439 612
440 613 $userId = $request->get('user_id');
441 614 $targetUser = User::findOrFail($userId);
442 - $xprofile = $targetUser->syncXProfile();
615 + $targetUser->syncXProfile();
616 + $xprofile = $targetUser->xprofile;
443 617
444 618 if ($xprofile && $xprofile->status != 'active') {
445 619 return $this->sendError([
446 620 'message' => __('Selected user is not active', 'fluent-community')
@@ -446,16 +620,15 @@
446 620 'message' => __('Selected user is not active', 'fluent-community')
447 621 ]);
448 622 }
449 623
450 - $admin = User::find(get_current_user_id());
451 - $admin->verifySpacePermission('can_add_member', $space);
452 -
453 624 $pivot = SpaceUserPivot::bySpace($space->id)
454 625 ->byUser($userId)
455 626 ->first();
456 627
457 - $role = $request->get('role', 'member');
628 + $allowedRoles = ['member', 'moderator', 'admin'];
629 + $requestedRole = $request->get('role', 'member');
630 + $role = in_array($requestedRole, $allowedRoles, true) ? $requestedRole : 'member';
458 631
459 632 if ($pivot) {
460 633 if ($pivot->status == 'active') {
461 634 if ($role != $pivot->role) {
@@ -464,14 +637,14 @@
464 637
465 638 do_action('fluent_community/space/member/role_updated', $space, $pivot);
466 639
467 640 return [
468 - 'message' => 'Member role updated'
641 + 'message' => __('Member role updated', 'fluent-community')
469 642 ];
470 643 }
471 644
472 645 return $this->sendError([
473 - 'message' => 'Selected user is already a member of this community'
646 + 'message' => __('Selected user is already a member of this community', 'fluent-community')
474 647 ]);
475 648 }
476 649
477 650 $pivot->status = 'active';
@@ -482,9 +655,9 @@
482 655 do_action('fluent_community/space/member/role_updated', $space, $pivot);
483 656 }
484 657
485 658 return [
486 - 'message' => 'Member approved'
659 + 'message' => __('Member approved', 'fluent-community')
487 660 ];
488 661 }
489 662
490 663 $space->members()->attach($userId, [
@@ -496,9 +669,9 @@
496 669
497 670 do_action('fluent_community/space/joined', $space, $userId, 'by_admin');
498 671
499 672 return [
500 - 'message' => 'User has been added to this community'
673 + 'message' => __('User has been added to this Space', 'fluent-community')
501 674 ];
502 675 }
503 676
504 677 public function removeMember(Request $request, $slug)
@@ -506,17 +679,14 @@
506 679 $space = Space::where('slug', $slug)->first();
507 680
508 681 if (!$space) {
509 682 return $this->sendError([
510 - 'message' => 'Space not found'
683 + 'message' => __('Space not found', 'fluent-community')
511 684 ]);
512 685 }
513 686
514 687 $userId = $request->get('user_id');
515 688
516 - $admin = User::find(get_current_user_id());
517 - $admin->verifySpacePermission('can_remove_member', $space);
518 -
519 689 $pivot = SpaceUserPivot::bySpace($space->id)
520 690 ->byUser($userId)
521 691 ->first();
522 692
@@ -521,9 +691,9 @@
521 691 ->first();
522 692
523 693 if (!$pivot) {
524 694 return $this->sendError([
525 - 'message' => 'Selected user is not a member of this community'
695 + 'message' => __('Selected user is not a member of this community', 'fluent-community')
526 696 ]);
527 697 }
528 698
529 699 $pivot->delete();
@@ -551,25 +721,53 @@
551 721
552 722 $isMod = $currentUser->isCommunityModerator() && current_user_can('list_users');
553 723
554 724 $spaceId = $request->get('space_id');
725 + $space = Space::findOrFail($spaceId);
555 726
727 + if (!$space->verifyUserPermisson($currentUser, 'can_add_member', false)) {
728 + return $this->sendError([
729 + 'message' => __('You are not allowed to add members to this space', 'fluent-community')
730 + ]);
731 + }
732 +
556 733 $selects = ['ID', 'display_name'];
557 -
558 734 if ($isMod) {
559 735 $selects[] = 'user_email';
560 736 }
561 737
562 - $users = User::whereDoesntHave('spaces', function ($q) use ($spaceId) {
563 - return $q->where('space_id', $spaceId);
564 - })
565 - ->select($selects)
566 - ->searchBy($request->get('search'))
567 - ->paginate();
738 + $userQuery = User::select(['ID'])
739 + ->whereDoesntHave('space_pivot', function ($q) use ($space) {
740 + $q->where('space_id', $space->id);
741 + })
742 + ->limit(100)
743 + ->searchBy($request->getSafe('search'));
568 744
569 - return [
745 + if (is_multisite()) {
746 + global $wpdb;
747 + $blogId = get_current_blog_id();
748 + $blogPrefix = $wpdb->get_blog_prefix($blogId);
749 + $userQuery->whereHas('usermeta', function ($q) use ($blogPrefix) {
750 + $q->where('meta_key', $blogPrefix . 'capabilities');
751 + });
752 + }
753 +
754 + $userIds = $userQuery->get()
755 + ->pluck('ID')
756 + ->toArray();
757 +
758 + if ($userIds) {
759 + update_meta_cache('user', $userIds);
760 + }
761 +
762 + $users = User::select($selects)
763 + ->whereIn('ID', $userIds)
764 + ->paginate(100);
765 +
766 + $data = [
570 767 'users' => $users
571 768 ];
769 + return apply_filters('fluent_community/space_non_members_api_response', $data, $request->all());
572 770 }
573 771
574 772 public function updateLinks(Request $request, $slug)
575 773 {
@@ -576,14 +774,12 @@
576 774 $space = Space::where('slug', $slug)->first();
577 775
578 776 if (!$space) {
579 777 return $this->sendError([
580 - 'message' => 'Space not found'
778 + 'message' => __('Space not found', 'fluent-community')
581 779 ]);
582 780 }
583 781
584 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
585 -
586 782 $links = $request->get('links', []);
587 783
588 784 $links = array_map(function ($link) {
589 785 return CustomSanitizer::santizeLinkItem($link);
@@ -594,46 +790,117 @@
594 790 $space->settings = $settings;
595 791 $space->save();
596 792
597 793 return [
598 - 'message' => __('Links has been updated for the space', 'fluent-community'),
794 + 'message' => __('Links have been updated for the space', 'fluent-community'),
599 795 'links' => $links
600 796 ];
601 797 }
602 798
603 - public function getSpaceGroups(Request $request)
799 + public function getPrimaryMenu(Request $request, $slug)
604 800 {
605 - $user = $this->getUser(true);
606 - if (!$user->isCommunityModerator()) {
801 + $space = Space::where('slug', $slug)->first();
802 +
803 + if (!$space) {
607 804 return $this->sendError([
608 - 'message' => 'You are not allowed to create space group'
805 + 'message' => __('Space not found', 'fluent-community'),
609 806 ]);
610 807 }
611 808
809 + // Lighter than formatSpaceData(), but listeners like fcom-chat need membership too.
612 810 $user = $this->getUser();
811 + $space->permissions = $space->getUserPermissions($user);
812 + $space->membership = $space->getMembership($user ? $user->ID : null);
613 813
814 + return [
815 + 'menu_items' => SpaceMenuService::getManagerItems($space),
816 + ];
817 + }
818 +
819 + public function updatePrimaryMenu(Request $request, $slug)
820 + {
821 + $space = Space::where('slug', $slug)->first();
822 +
823 + if (!$space) {
824 + return $this->sendError([
825 + 'message' => __('Space not found', 'fluent-community'),
826 + ]);
827 + }
828 +
829 + $incoming = $request->get('menu_items', []);
830 +
831 + if (!is_array($incoming)) {
832 + return $this->sendError([
833 + 'message' => __('Menu items must be a list.', 'fluent-community'),
834 + ], 422);
835 + }
836 +
837 + $menuItems = CustomSanitizer::sanitizeSpaceMenuItems($incoming);
838 +
839 + SpaceMenuService::storeMenu($space, $menuItems);
840 +
841 + // No re-fetch needed — unlike formatSpaceData(), this doesn't make $space unsaveable.
842 + $user = $this->getUser();
843 + $space->permissions = $space->getUserPermissions($user);
844 + $space->membership = $space->getMembership($user ? $user->ID : null);
845 +
846 + return [
847 + 'message' => __('Menu has been updated for the space', 'fluent-community'),
848 + 'menu_items' => SpaceMenuService::getManagerItems($space),
849 + ];
850 + }
851 +
852 + public function getSpaceGroups(Request $request)
853 + {
854 + if ($request->get('options_only')) {
855 + $groups = SpaceGroup::orderBy('serial', 'ASC')
856 + ->select(['id', 'title'])
857 + ->get();
858 +
859 + return [
860 + 'groups' => $groups
861 + ];
862 + }
863 +
864 + $user = $this->getUser();
865 +
614 866 $groups = Helper::getAllCommunityGroups($user, false);
615 867
616 868 foreach ($groups as $group) {
617 869 foreach ($group->spaces as $space) {
618 870 $space->permalink = $space->getPermalink();
871 + if ($space->type === 'community') {
872 + $space = $space->formatSpaceData($user);
873 + } else {
874 + $space->topics = Utility::getTopicsBySpaceId($space->id);
875 + }
619 876 }
620 877 }
621 878
622 - return [
623 - 'groups' => $groups
879 + $orphanedSpaces = BaseSpace::withoutGlobalScopes()
880 + ->whereNull('parent_id')
881 + ->whereIn('type', ['community', 'course'])
882 + ->orderBy('title', 'ASC')
883 + ->get();
884 +
885 + foreach ($orphanedSpaces as $space) {
886 + $space->permalink = $space->getPermalink();
887 + if ($space->type === 'community') {
888 + $space = $space->formatSpaceData($user);
889 + } else {
890 + $space->topics = Utility::getTopicsBySpaceId($space->id);
891 + }
892 + }
893 +
894 + $data = [
895 + 'groups' => $groups,
896 + 'orphaned_spaces' => $orphanedSpaces
624 897 ];
898 + return apply_filters('fluent_community/space_groups_api_response', $data, $request->all());
625 899 }
626 900
627 901 public function createSpaceGroup(Request $request)
628 902 {
629 - $user = $this->getUser(true);
630 - if (!$user->isCommunityModerator()) {
631 - return $this->sendError([
632 - 'message' => 'You are not allowed to create space group'
633 - ]);
634 - }
635 -
636 903 $data = $request->all();
637 904
638 905 $this->validate($data, [
639 906 'title' => 'required|unique:fcom_spaces,title',
@@ -639,13 +906,28 @@
639 906 'title' => 'required|unique:fcom_spaces,title',
640 907 'slug' => 'required|unique:fcom_spaces,slug'
641 908 ]);
642 909
910 + $title = sanitize_text_field(Arr::get($data, 'title', ''));
911 + $slug = sanitize_title(Arr::get($data, 'slug', ''));
912 + $desc = sanitize_textarea_field(Arr::get($data, 'description', ''));
643 913
914 + if (!$title) {
915 + return $this->sendError([
916 + 'message' => __('Please enter a valid group title.', 'fluent-community')
917 + ]);
918 + }
919 +
920 + if (!$slug) {
921 + return $this->sendError([
922 + 'message' => __('Please enter a valid group slug.', 'fluent-community')
923 + ]);
924 + }
925 +
644 926 $formattedData = [
645 - 'title' => sanitize_text_field($data['title']),
646 - 'slug' => sanitize_title($data['slug']),
647 - 'description' => sanitize_textarea_field($data['description']),
927 + 'title' => $title,
928 + 'slug' => $slug,
929 + 'description' => $desc,
648 930 'status' => 'active',
649 931 'type' => 'space_group',
650 932 'settings' => [
651 933 'always_show_spaces' => Arr::get($data, 'settings.always_show_spaces', 'yes'),
@@ -662,15 +944,8 @@
662 944 }
663 945
664 946 public function updateSpaceGroup(Request $request, $groupId)
665 947 {
666 - $user = $this->getUser();
667 - if (!$user || !$user->isCommunityModerator()) {
668 - return $this->sendError([
669 - 'message' => 'You are not allowed to create space group'
670 - ]);
671 - }
672 -
673 948 $group = SpaceGroup::findOrFail($groupId);
674 949 $data = $request->all();
675 950
676 951 $this->validate($data, [
@@ -682,9 +957,9 @@
682 957 ->first();
683 958
684 959 if ($taken) {
685 960 return $this->sendError([
686 - 'message' => 'The title is already taken. Please use a different title'
961 + 'message' => __('The title is already taken. Please use a different title', 'fluent-community')
687 962 ]);
688 963 }
689 964
690 965 $formattedData = [
@@ -699,9 +974,9 @@
699 974
700 975 $group->fill($formattedData)->save();
701 976
702 977 return [
703 - 'message' => __('Space group has been created updated', 'fluent-community'),
978 + 'message' => __('Space group has been updated', 'fluent-community'),
704 979 'group' => $group
705 980 ];
706 981 }
707 982
@@ -706,21 +981,13 @@
706 981 }
707 982
708 983 public function deleteSpaceGroup(Request $request, $groupId)
709 984 {
710 -
711 - $user = $this->getUser();
712 - if (!$user || !$user->isCommunityModerator()) {
713 - return $this->sendError([
714 - 'message' => 'You are not allowed to create space group'
715 - ]);
716 - }
717 -
718 985 $group = SpaceGroup::findOrFail($groupId);
719 986
720 987 if (!$group->spaces->isEmpty()) {
721 988 return $this->sendError([
722 - 'message' => 'You can not delete this group. It has spaces'
989 + 'message' => __('You cannot delete this group. It has spaces', 'fluent-community')
723 990 ]);
724 991 }
725 992
726 993 $group->delete();
@@ -741,9 +1008,9 @@
741 1008 ]);
742 1009 }
743 1010
744 1011 return [
745 - 'message' => __('Space group indexes has been updated', 'fluent-community')
1012 + 'message' => __('Space group indexes have been updated.', 'fluent-community')
746 1013 ];
747 1014
748 1015 }
749 1016
@@ -758,9 +1025,9 @@
758 1025 ]);
759 1026 }
760 1027
761 1028 return [
762 - 'message' => __('Space indexes has been updated', 'fluent-community')
1029 + 'message' => __('Space indexes have been updated.', 'fluent-community')
763 1030 ];
764 1031 }
765 1032
766 1033 public function moveSpace(Request $request)
@@ -771,9 +1038,9 @@
771 1038 $space = BaseSpace::withoutGlobalScopes()->findOrFail($spaceId);
772 1039 $group = SpaceGroup::findOrFail($groupId);
773 1040
774 1041 $space->update([
775 - 'parent_id' => $groupId
1042 + 'parent_id' => $group->id
776 1043 ]);
777 1044
778 1045 return [
779 1046 'message' => __('Space has been moved successfully', 'fluent-community')
@@ -781,28 +1048,58 @@
781 1048 }
782 1049
783 1050 public function getLockScreenSettings(Request $request, $spaceSlug)
784 1051 {
785 - $space = Space::where('slug', $spaceSlug)->firstOrFail();
1052 + /** @var Space $space */
1053 + $space = Space::where('slug', $spaceSlug)->first();
1054 +
1055 + $userId = $this->getUser() ? $this->getUser()->ID : null;
1056 +
1057 + if (!$space || ($space->privacy == 'secret' && !$space->getMembership($userId) && !$space->isAdmin($userId, true))) {
1058 + return $this->sendError([
1059 + 'message' => __('Space not found', 'fluent-community')
1060 + ], 404);
1061 + }
1062 +
786 1063 $lockscreen = $space->getLockscreen();
787 1064
1065 + $lockscreen = apply_filters('fluent_community/get_lockscreen_settings', $lockscreen, $space);
1066 +
788 1067 return [
789 1068 'lockscreen' => $lockscreen
790 1069 ];
791 1070 }
792 1071
793 - public function updateLockscreenSettings(Request $request, $spaceSlug)
1072 + public function getMetaSettings(Request $request, $spaceSlug)
794 1073 {
795 1074 $space = Space::where('slug', $spaceSlug)->firstOrFail();
1075 + $metaSettings = apply_filters('fluent_community/space/meta_fields', [], $space);
796 1076
797 - $settingFields = $request->get('lockscreen', []);
1077 + if (!$metaSettings) {
1078 + return [
1079 + 'meta_settings' => null
1080 + ];
1081 + }
798 1082
799 - $formattedFields = LockscreenService::formatLockscreenFields($settingFields);
1083 + return [
1084 + 'meta_settings' => $metaSettings
1085 + ];
1086 + }
800 1087
801 - $space->setLockscreen($formattedFields);
1088 + private function getActiveMemberCounts($spaceIds)
1089 + {
1090 + if (!$spaceIds) {
1091 + return [];
1092 + }
802 1093
803 - return [
804 - 'message' => 'Lockscreen settings has been updated successfully.',
805 - 'community' => $space
806 - ];
1094 + return SpaceUserPivot::whereIn('space_id', $spaceIds)
1095 + ->where('status', 'active')
1096 + ->whereHas('xprofile', function ($q) {
1097 + $q->where('status', 'active');
1098 + })
1099 + ->whereHas('user')
1100 + ->selectRaw('space_id, COUNT(*) as total')
1101 + ->groupBy('space_id')
1102 + ->pluck('total', 'space_id')
1103 + ->toArray();
807 1104 }
808 1105 }