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 +166 -21 2.4.012.11.0 View file →
@@ -8,8 +8,9 @@
8 8 use FluentCommunity\App\Models\SpaceGroup;
9 9 use FluentCommunity\App\Models\User;
10 10 use FluentCommunity\App\Services\CustomSanitizer;
11 11 use FluentCommunity\App\Services\Helper;
12 +use FluentCommunity\App\Services\SpaceMenuService;
12 13 use FluentCommunity\App\Functions\Utility;
13 14 use FluentCommunity\App\Services\ProfileHelper;
14 15 use FluentCommunity\Framework\Http\Request\Request;
15 16 use FluentCommunity\App\Models\Comment;
@@ -53,8 +54,14 @@
53 54 'slug' => 'unique:fcom_spaces,slug',
54 55 'privacy' => 'required|in:public,private,secret'
55 56 ]);
56 57
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 +
57 64 $spaceGroup = null;
58 65 if (!empty($data['parent_id'])) {
59 66 $spaceGroup = SpaceGroup::findOrFail($data['parent_id']);
60 67 $serial = BaseSpace::query()->withoutGlobalScopes()->where('parent_id', $spaceGroup->id)->max('serial') + 1;
@@ -153,9 +160,10 @@
153 160 $q->where('user_id', get_current_user_id());
154 161 }])
155 162 ->where(function ($q) {
156 163 $q->whereHas('space_pivot', function ($q) {
157 - $q->where('user_id', get_current_user_id());
164 + $q->where('user_id', get_current_user_id())
165 + ->where('status', 'active');
158 166 })
159 167 ->orWhereIn('privacy', ['public', 'private']);
160 168 })
161 169 ->when($type == 'joined', function ($q) {
@@ -179,8 +187,10 @@
179 187
180 188 $memberCounts = $this->getActiveMemberCounts($spaces->pluck('id')->toArray());
181 189
182 190 foreach ($spaces as $space) {
191 + $space->description_rendered = wpautop($space->description);
192 +
183 193 $shouldHideMembersCount = Arr::get($space->settings, 'hide_members_count') == 'yes';
184 194 $canViewMembers = $currentUser && $space->verifyUserPermisson($currentUser, 'can_view_members', false);
185 195
186 196 if ($shouldHideMembersCount && !$canViewMembers) {
@@ -188,9 +198,8 @@
188 198 continue;
189 199 }
190 200
191 201 $space->members_count = (int)Arr::get($memberCounts, $space->id, 0);
192 - $space->description_rendered = wpautop($space->description);
193 202 }
194 203
195 204 $data = [
196 205 'spaces' => $spaces,
@@ -201,11 +210,23 @@
201 210 }
202 211
203 212 public function getAllSpaces(Request $request)
204 213 {
205 - $spaces = Space::paginate();
214 + $currentUser = $this->getUser();
206 215
207 - $currentUser = $this->getUser();
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');
223 + })
224 + ->orWhereIn('privacy', ['public', 'private']);
225 + });
226 + }
227 + $spaces = $spacesQuery->paginate();
228 +
208 229 $memberCounts = $this->getActiveMemberCounts($spaces->pluck('id')->toArray());
209 230
210 231 foreach ($spaces as $space) {
211 232 $shouldHideMembersCount = Arr::get($space->settings, 'hide_members_count') == 'yes';
@@ -229,17 +250,18 @@
229 250
230 251 public function getBySlug(Request $request, $spaceSlug)
231 252 {
232 253 $user = $this->getUser();
233 - $space = Space::where('slug', $spaceSlug)
234 - ->firstOrFail();
254 + $space = Space::where('slug', $spaceSlug)->first();
235 255
236 256 $userId = $user ? $user->ID : null;
237 - if ($space->privacy == 'secret' && !$space->getMembership($userId) && !$space->isAdmin($userId, true)) {
257 +
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))) {
238 261 return $this->sendError([
239 - 'message' => __('You are not allowed to view this space', 'fluent-community'),
240 - 'error_type' => 'restricted'
241 - ]);
262 + 'message' => __('Space not found', 'fluent-community')
263 + ], 404);
242 264 }
243 265
244 266 $space = $space->formatSpaceData($user);
245 267
@@ -264,8 +286,14 @@
264 286 }
265 287
266 288 $data = $request->get('data', []);
267 289
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 + }
295 +
268 296 if (!empty($data['slug'])) {
269 297 $taken = Space::where('slug', $data['slug'])
270 298 ->where('id', '!=', $space->id)
271 299 ->first();
@@ -276,8 +304,24 @@
276 304 ]);
277 305 }
278 306 }
279 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 +
280 324 $mediaTypes = ['cover_photo', 'logo'];
281 325 foreach ($mediaTypes as $type) {
282 326 if (!empty($data[$type])) {
283 327 $media = Helper::getMediaFromUrl($data[$type]);
@@ -344,8 +388,9 @@
344 388 }
345 389
346 390 public function getMembers(Request $request, $slug)
347 391 {
392 + /** @var Space $space */
348 393 $space = Space::where('slug', $slug)
349 394 ->firstOrFail();
350 395
351 396 $user = $this->getUser();
@@ -383,20 +428,37 @@
383 428 ], $pendingRequests, $request->all());
384 429 }
385 430 }
386 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 +
387 446 $spaceMembers = SpaceUserPivot::bySpace($space->id)
388 447 ->whereHas('xprofile', function ($q) use ($search) {
389 - return $q->searchBy($search)
390 - ->where('status', 'active');
448 + $q->searchBy($search)->where('status', 'active');
391 449 })
450 + ->where('fcom_space_user.status', 'active')
392 451 ->with(['xprofile' => function ($q) {
393 452 $q->select(ProfileHelper::getXProfilePublicFields());
394 453 }])
395 - ->where('status', 'active')
396 - ->orderBy('created_at', 'ASC')
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)
397 459 ->paginate();
398 -
460 +
399 461 return apply_filters('fluent_community/space_members_api_response', [
400 462 'members' => $spaceMembers,
401 463 'pending_count' => $pendingCount
402 464 ], $spaceMembers, $request->all());
@@ -422,10 +484,11 @@
422 484 ]);
423 485 }
424 486
425 487 $roles = $user->getCommunityRoles();
488 + $isPrivileged = !!array_intersect($roles, ['admin', 'moderator']);
426 489
427 - if (!$roles && $space->privacy == 'secret') {
490 + if (!$isPrivileged && $space->privacy == 'secret') {
428 491 return $this->sendError([
429 492 'message' => __('You are not allowed to join this space', 'fluent-community')
430 493 ]);
431 494 }
@@ -430,9 +493,9 @@
430 493 ]);
431 494 }
432 495
433 496 $status = 'active';
434 - if (!$roles) {
497 + if (!$isPrivileged) {
435 498 if ($space->privacy != 'public') {
436 499 $status = apply_filters('fluent_community/space/join_status_for_private', 'pending', $space, $user);
437 500
438 501 if (!in_array($status, ['pending', 'active'])) {
@@ -691,8 +754,12 @@
691 754 $userIds = $userQuery->get()
692 755 ->pluck('ID')
693 756 ->toArray();
694 757
758 + if ($userIds) {
759 + update_meta_cache('user', $userIds);
760 + }
761 +
695 762 $users = User::select($selects)
696 763 ->whereIn('ID', $userIds)
697 764 ->paginate(100);
698 765
@@ -728,8 +795,61 @@
728 795 'links' => $links
729 796 ];
730 797 }
731 798
799 + public function getPrimaryMenu(Request $request, $slug)
800 + {
801 + $space = Space::where('slug', $slug)->first();
802 +
803 + if (!$space) {
804 + return $this->sendError([
805 + 'message' => __('Space not found', 'fluent-community'),
806 + ]);
807 + }
808 +
809 + // Lighter than formatSpaceData(), but listeners like fcom-chat need membership too.
810 + $user = $this->getUser();
811 + $space->permissions = $space->getUserPermissions($user);
812 + $space->membership = $space->getMembership($user ? $user->ID : null);
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 +
732 852 public function getSpaceGroups(Request $request)
733 853 {
734 854 if ($request->get('options_only')) {
735 855 $groups = SpaceGroup::orderBy('serial', 'ASC')
@@ -786,13 +906,28 @@
786 906 'title' => 'required|unique:fcom_spaces,title',
787 907 'slug' => 'required|unique:fcom_spaces,slug'
788 908 ]);
789 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', ''));
790 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 +
791 926 $formattedData = [
792 - 'title' => sanitize_text_field($data['title']),
793 - 'slug' => sanitize_title($data['slug']),
794 - 'description' => sanitize_textarea_field($data['description']),
927 + 'title' => $title,
928 + 'slug' => $slug,
929 + 'description' => $desc,
795 930 'status' => 'active',
796 931 'type' => 'space_group',
797 932 'settings' => [
798 933 'always_show_spaces' => Arr::get($data, 'settings.always_show_spaces', 'yes'),
@@ -913,9 +1048,19 @@
913 1048 }
914 1049
915 1050 public function getLockScreenSettings(Request $request, $spaceSlug)
916 1051 {
917 - $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 +
918 1063 $lockscreen = $space->getLockscreen();
919 1064
920 1065 $lockscreen = apply_filters('fluent_community/get_lockscreen_settings', $lockscreen, $space);
921 1066