PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / trunk
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses vtrunk
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 1.1.0 All 77 releases
fluent-community / app / Http / Controllers / SpaceController.php

SpaceController.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses trunk, at app/Http/Controllers/SpaceController.php

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