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 +480 -171 1.0.912.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,57 +27,86 @@
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 - $space = Space::create([
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 +
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 - ],
77 - 'parent_id' => $spaceGroup->id,
78 - '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
79 88 ]);
80 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 +
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 + }
108 +
81 109 $imageTypes = ['cover_photo', 'logo'];
82 110 $metaData = [];
83 111 foreach ($imageTypes as $type) {
84 112 if (!empty($data[$type])) {
@@ -103,10 +131,16 @@
103 131 $space->members()->attach(get_current_user_id(), [
104 132 'role' => 'admin'
105 133 ]);
106 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 +
107 142 $currentUser->cacheAccessSpaces();
108 -
109 143 do_action('fluent_community/space/created', $space, $data);
110 144
111 145 return [
112 146 'message' => __('Space has been created successfully', 'fluent-community'),
@@ -115,56 +149,130 @@
115 149 }
116 150
117 151 public function discover(Request $request)
118 152 {
119 - $spaces = Space::orderBy('title', 'ASC')
120 - ->with(['space_pivot' => function ($q) {
121 - $q->where('user_id', get_current_user_id());
122 - }])
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 + }])
123 162 ->where(function ($q) {
124 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) {
125 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');
126 223 })
127 224 ->orWhereIn('privacy', ['public', 'private']);
128 - })
129 - ->get();
225 + });
226 + }
227 + $spaces = $spacesQuery->paginate();
130 228
229 + $memberCounts = $this->getActiveMemberCounts($spaces->pluck('id')->toArray());
230 +
131 231 foreach ($spaces as $space) {
132 - $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]);
133 244 }
134 245
135 - return [
246 + return apply_filters('fluent_community/all_spaces_api_response', [
136 247 'spaces' => $spaces
137 - ];
248 + ], $request->all());
138 249 }
139 250
140 251 public function getBySlug(Request $request, $spaceSlug)
141 252 {
142 253 $user = $this->getUser();
143 - $space = Space::where('slug', $spaceSlug)
144 - ->firstOrFail();
254 + $space = Space::where('slug', $spaceSlug)->first();
145 255
146 - $space->permissions = $space->getUserPermissions($user);
147 - $space->description_rendered = FeedsHelper::mdToHtml($space->description);
148 - $space->membership = $space->getMembership(get_current_user_id());
149 - $space->topics = Utility::getTopicsBySpaceId($space->id);
256 + $userId = $user ? $user->ID : null;
150 257
151 - if (!Helper::isSiteAdmin()) {
152 - $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);
153 264 }
154 265
155 - if ($space->privacy == 'secret' && !$space->membership) {
156 - return $this->sendError([
157 - 'message' => __('You are not allowed to view this space', 'fluent-community'),
158 - 'error_type' => 'restricted'
159 - ]);
160 - }
266 + $space = $space->formatSpaceData($user);
161 267
162 268 do_action_ref_array('fluent_community/space', [&$space]);
163 269
164 - return [
270 + $data = [
165 271 'space' => $space
166 272 ];
273 +
274 + return apply_filters('fluent_community/space_api_response', $data, $request->all());
167 275 }
168 276
169 277 public function patchBySlug(Request $request, $slug)
170 278 {
@@ -172,42 +280,57 @@
172 280 ->first();
173 281
174 282 if (!$space) {
175 283 return $this->sendError([
176 - 'message' => 'Space not found'
284 + 'message' => __('Space not found', 'fluent-community')
177 285 ]);
178 286 }
179 287
180 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
288 + $data = $request->get('data', []);
181 289
182 - $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 + }
183 295
184 - if (!empty($data['title'])) {
185 - $taken = Space::where('title', $data['title'])
296 + if (!empty($data['slug'])) {
297 + $taken = Space::where('slug', $data['slug'])
186 298 ->where('id', '!=', $space->id)
187 299 ->first();
300 +
188 301 if ($taken) {
189 302 return $this->sendError([
190 - 'message' => 'Space title is already taken. Please use a different title'
303 + 'message' => __('Slug is already taken. Please use a different slug', 'fluent-community')
191 304 ]);
192 305 }
193 306 }
194 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 +
195 324 $mediaTypes = ['cover_photo', 'logo'];
196 325 foreach ($mediaTypes as $type) {
197 326 if (!empty($data[$type])) {
198 327 $media = Helper::getMediaFromUrl($data[$type]);
199 - if (!$media) {
328 + if (!$media || $media->is_active) {
200 329 unset($data[$type]);
201 330 continue;
202 331 }
203 332
204 - if (!$media || $media->is_active) {
205 - return $this->sendError([
206 - 'message' => 'Invalid media image. Please upload a new one.'
207 - ]);
208 - }
209 -
210 333 $data[$type] = $media->public_url;
211 334
212 335 $media->update([
213 336 'is_active' => true,
@@ -219,30 +342,67 @@
219 342 $data[$type] = '';
220 343 }
221 344 }
222 345
223 - $space->updateCustomData($data, true);
346 + $data = apply_filters('fluent_community/space/update_data', $data, $space);
224 347
348 + if (isset($data['parent_id']) && !$data['parent_id']) {
349 + $data['parent_id'] = '';
350 + }
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 +
225 360 if (Arr::has($data, 'topic_ids')) {
226 - $topicIds = Arr::get($data, 'topic_ids', []);
361 + $topicsConfig = Helper::getTopicsConfig();
362 + $topicIds = (array)Arr::get($data, 'topic_ids', []);
363 + $topicIds = array_slice($topicIds, 0, $topicsConfig['max_topics_per_space']);
227 364 $space->syncTopics($topicIds);
228 365 }
229 366
230 367 do_action('fluent_community/space/updated', $space, $data);
368 + $slugUpdated = $slug != $space->slug;
231 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 +
232 377 return [
233 - 'message' => __('Space has been updated', 'fluent-community')
378 + 'message' => __('Settings have been updated', 'fluent-community'),
379 + 'redirect_url' => $slugUpdated ? $space->getPermalink() : ''
234 380 ];
235 381 }
236 382
383 + public function patchById(Request $request, $id)
384 + {
385 + $space = Space::findOrFail($id);
386 +
387 + return $this->patchBySlug($request, $space->slug);
388 + }
389 +
237 390 public function getMembers(Request $request, $slug)
238 391 {
392 + /** @var Space $space */
239 393 $space = Space::where('slug', $slug)
240 394 ->firstOrFail();
241 395
242 396 $user = $this->getUser();
243 - $space->verifyUserPermisson($user, 'can_view_members');
244 397
398 + if (!$space->verifyUserPermisson($user, 'can_view_members', false)) {
399 + return $this->sendError([
400 + 'message' => __('You are not allowed to view members of this space', 'fluent-community'),
401 + 'permission_failed' => true
402 + ]);
403 + }
404 +
245 405 $search = $request->getSafe('search', 'sanitize_text_field');
246 406
247 407 $pendingCount = 0;
248 408 if ($user && $user->can('can_add_member', $space)) {
@@ -261,30 +421,48 @@
261 421 }])
262 422 ->where('status', 'pending')
263 423 ->paginate();
264 424
265 - return [
425 + return apply_filters('fluent_community/space_members_api_response', [
266 426 'members' => $pendingRequests,
267 427 'pending_count' => $pendingCount
268 - ];
428 + ], $pendingRequests, $request->all());
269 429 }
270 430 }
271 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 +
272 446 $spaceMembers = SpaceUserPivot::bySpace($space->id)
273 447 ->whereHas('xprofile', function ($q) use ($search) {
274 - return $q->searchBy($search)
275 - ->where('status', 'active');
448 + $q->searchBy($search)->where('status', 'active');
276 449 })
450 + ->where('fcom_space_user.status', 'active')
277 451 ->with(['xprofile' => function ($q) {
278 452 $q->select(ProfileHelper::getXProfilePublicFields());
279 453 }])
280 - ->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)
281 459 ->paginate();
282 460
283 - return [
461 + return apply_filters('fluent_community/space_members_api_response', [
284 462 'members' => $spaceMembers,
285 463 'pending_count' => $pendingCount
286 - ];
464 + ], $spaceMembers, $request->all());
287 465 }
288 466
289 467 public function join(Request $request, $slug)
290 468 {
@@ -291,9 +469,9 @@
291 469 $space = Space::where('slug', $slug)->first();
292 470
293 471 if (!$space) {
294 472 return $this->sendError([
295 - 'message' => 'Space not found'
473 + 'message' => __('Space not found', 'fluent-community')
296 474 ]);
297 475 }
298 476
299 477 $user = $this->getUser();
@@ -301,24 +479,29 @@
301 479 $membership = $space->getMembership(get_current_user_id());
302 480
303 481 if ($membership) {
304 482 return $this->sendError([
305 - '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')
306 484 ]);
307 485 }
308 486
309 487 $roles = $user->getCommunityRoles();
488 + $isPrivileged = !!array_intersect($roles, ['admin', 'moderator']);
310 489
311 - if (!$roles && $space->privacy == 'secret') {
490 + if (!$isPrivileged && $space->privacy == 'secret') {
312 491 return $this->sendError([
313 - 'message' => 'You are not allowed to join this space'
492 + 'message' => __('You are not allowed to join this space', 'fluent-community')
314 493 ]);
315 494 }
316 495
317 496 $status = 'active';
318 - if (!$roles) {
497 + if (!$isPrivileged) {
319 498 if ($space->privacy != 'public') {
320 499 $status = apply_filters('fluent_community/space/join_status_for_private', 'pending', $space, $user);
500 +
501 + if (!in_array($status, ['pending', 'active'])) {
502 + $status = 'pending';
503 + }
321 504 }
322 505 $role = 'member';
323 506 } else {
324 507 $role = $user->isCommunityAdmin() ? 'admin' : 'moderator';
@@ -323,8 +506,9 @@
323 506 } else {
324 507 $role = $user->isCommunityAdmin() ? 'admin' : 'moderator';
325 508 }
326 509
510 +
327 511 $space->members()->attach(get_current_user_id(), [
328 512 'role' => $role,
329 513 'status' => $status
330 514 ]);
@@ -339,9 +523,9 @@
339 523
340 524 $user->cacheAccessSpaces();
341 525
342 526 return [
343 - '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'),
344 528 'membership' => $space->membership
345 529 ];
346 530 }
347 531
@@ -351,9 +535,9 @@
351 535 $space = Space::where('slug', $slug)->first();
352 536
353 537 if (!$space) {
354 538 return $this->sendError([
355 - 'message' => 'Space not found'
539 + 'message' => __('Space not found', 'fluent-community')
356 540 ]);
357 541 }
358 542
359 543 $membership = $space->getMembership($user->ID);
@@ -359,9 +543,9 @@
359 543 $membership = $space->getMembership($user->ID);
360 544
361 545 if (!$membership) {
362 546 return $this->sendError([
363 - 'message' => 'You are not a member of this community'
547 + 'message' => __('You are not a member of this community', 'fluent-community')
364 548 ]);
365 549 }
366 550
367 551 Helper::removeFromSpace($space, $user->ID, 'self');
@@ -370,24 +554,18 @@
370 554 'message' => __('You have left this space', 'fluent-community')
371 555 ];
372 556 }
373 557
374 - public function delete(Request $request, $slug)
558 + public function deleteBySlug(Request $request, $slug)
375 559 {
376 560 $space = Space::where('slug', $slug)->first();
377 561
378 562 if (!$space) {
379 563 return $this->sendError([
380 - 'message' => 'Space not found'
564 + 'message' => __('Space not found', 'fluent-community')
381 565 ]);
382 566 }
383 567
384 - if (!Helper::isSiteAdmin()) {
385 - return $this->sendError([
386 - 'message' => 'You are not allowed to delete this community'
387 - ]);
388 - }
389 -
390 568 do_action('fluent_community/space/before_delete', $space);
391 569
392 570 Comment::whereHas('post', function ($q) use ($space) {
393 571 $q->where('space_id', $space->id);
@@ -410,8 +588,15 @@
410 588 'message' => __('Space has been deleted successfully', 'fluent-community')
411 589 ];
412 590 }
413 591
592 + public function deleteById(Request $request, $id)
593 + {
594 + $space = Space::findOrFail($id);
595 +
596 + return $this->deleteBySlug($request, $space->slug);
597 + }
598 +
414 599 public function addMember(Request $request, $slug)
415 600 {
416 601 $space = Space::where('slug', $slug)->first();
417 602
@@ -416,9 +601,9 @@
416 601 $space = Space::where('slug', $slug)->first();
417 602
418 603 if (!$space) {
419 604 return $this->sendError([
420 - 'message' => 'Space not found'
605 + 'message' => __('Space not found', 'fluent-community')
421 606 ]);
422 607 }
423 608
424 609 $this->validate($request->all(), [
@@ -426,9 +611,10 @@
426 611 ]);
427 612
428 613 $userId = $request->get('user_id');
429 614 $targetUser = User::findOrFail($userId);
430 - $xprofile = $targetUser->syncXProfile();
615 + $targetUser->syncXProfile();
616 + $xprofile = $targetUser->xprofile;
431 617
432 618 if ($xprofile && $xprofile->status != 'active') {
433 619 return $this->sendError([
434 620 'message' => __('Selected user is not active', 'fluent-community')
@@ -434,16 +620,15 @@
434 620 'message' => __('Selected user is not active', 'fluent-community')
435 621 ]);
436 622 }
437 623
438 - $admin = User::find(get_current_user_id());
439 - $admin->verifySpacePermission('can_add_member', $space);
440 -
441 624 $pivot = SpaceUserPivot::bySpace($space->id)
442 625 ->byUser($userId)
443 626 ->first();
444 627
445 - $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';
446 631
447 632 if ($pivot) {
448 633 if ($pivot->status == 'active') {
449 634 if ($role != $pivot->role) {
@@ -452,20 +637,20 @@
452 637
453 638 do_action('fluent_community/space/member/role_updated', $space, $pivot);
454 639
455 640 return [
456 - 'message' => 'Member role updated'
641 + 'message' => __('Member role updated', 'fluent-community')
457 642 ];
458 643 }
459 644
460 645 return $this->sendError([
461 - 'message' => 'Selected user is already a member of this community'
646 + 'message' => __('Selected user is already a member of this community', 'fluent-community')
462 647 ]);
463 648 }
464 649
465 650 $pivot->status = 'active';
466 651 $pivot->save();
467 - do_action('fluent_community/space/joined', $space, $userId, 'approved');
652 + do_action('fluent_community/space/joined', $space, $userId, 'by_admin');
468 653
469 654 if ($role != 'member') {
470 655 do_action('fluent_community/space/member/role_updated', $space, $pivot);
471 656 }
@@ -470,9 +655,9 @@
470 655 do_action('fluent_community/space/member/role_updated', $space, $pivot);
471 656 }
472 657
473 658 return [
474 - 'message' => 'Member approved'
659 + 'message' => __('Member approved', 'fluent-community')
475 660 ];
476 661 }
477 662
478 663 $space->members()->attach($userId, [
@@ -484,9 +669,9 @@
484 669
485 670 do_action('fluent_community/space/joined', $space, $userId, 'by_admin');
486 671
487 672 return [
488 - 'message' => 'User has been added to this community'
673 + 'message' => __('User has been added to this Space', 'fluent-community')
489 674 ];
490 675 }
491 676
492 677 public function removeMember(Request $request, $slug)
@@ -494,17 +679,14 @@
494 679 $space = Space::where('slug', $slug)->first();
495 680
496 681 if (!$space) {
497 682 return $this->sendError([
498 - 'message' => 'Space not found'
683 + 'message' => __('Space not found', 'fluent-community')
499 684 ]);
500 685 }
501 686
502 687 $userId = $request->get('user_id');
503 688
504 - $admin = User::find(get_current_user_id());
505 - $admin->verifySpacePermission('can_remove_member', $space);
506 -
507 689 $pivot = SpaceUserPivot::bySpace($space->id)
508 690 ->byUser($userId)
509 691 ->first();
510 692
@@ -509,9 +691,9 @@
509 691 ->first();
510 692
511 693 if (!$pivot) {
512 694 return $this->sendError([
513 - 'message' => 'Selected user is not a member of this community'
695 + 'message' => __('Selected user is not a member of this community', 'fluent-community')
514 696 ]);
515 697 }
516 698
517 699 $pivot->delete();
@@ -524,9 +706,9 @@
524 706
525 707 do_action('fluent_community/space/user_left', $space, $userId, 'by_admin');
526 708
527 709 return [
528 - 'message' => 'User has been removed from this community'
710 + 'message' => __('User has been removed from this community', 'fluent-community')
529 711 ];
530 712 }
531 713
532 714 public function getOtherUsers(Request $request)
@@ -539,25 +721,53 @@
539 721
540 722 $isMod = $currentUser->isCommunityModerator() && current_user_can('list_users');
541 723
542 724 $spaceId = $request->get('space_id');
725 + $space = Space::findOrFail($spaceId);
543 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 +
544 733 $selects = ['ID', 'display_name'];
545 -
546 734 if ($isMod) {
547 735 $selects[] = 'user_email';
548 736 }
549 737
550 - $users = User::whereDoesntHave('spaces', function ($q) use ($spaceId) {
551 - return $q->where('space_id', $spaceId);
552 - })
553 - ->select($selects)
554 - ->searchBy($request->get('search'))
555 - ->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'));
556 744
557 - 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 = [
558 767 'users' => $users
559 768 ];
769 + return apply_filters('fluent_community/space_non_members_api_response', $data, $request->all());
560 770 }
561 771
562 772 public function updateLinks(Request $request, $slug)
563 773 {
@@ -564,14 +774,12 @@
564 774 $space = Space::where('slug', $slug)->first();
565 775
566 776 if (!$space) {
567 777 return $this->sendError([
568 - 'message' => 'Space not found'
778 + 'message' => __('Space not found', 'fluent-community')
569 779 ]);
570 780 }
571 781
572 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
573 -
574 782 $links = $request->get('links', []);
575 783
576 784 $links = array_map(function ($link) {
577 785 return CustomSanitizer::santizeLinkItem($link);
@@ -582,46 +790,117 @@
582 790 $space->settings = $settings;
583 791 $space->save();
584 792
585 793 return [
586 - 'message' => __('Links has been updated for the space', 'fluent-community'),
794 + 'message' => __('Links have been updated for the space', 'fluent-community'),
587 795 'links' => $links
588 796 ];
589 797 }
590 798
591 - public function getSpaceGroups(Request $request)
799 + public function getPrimaryMenu(Request $request, $slug)
592 800 {
593 - $user = $this->getUser(true);
594 - if (!$user->isCommunityModerator()) {
801 + $space = Space::where('slug', $slug)->first();
802 +
803 + if (!$space) {
595 804 return $this->sendError([
596 - 'message' => 'You are not allowed to create space group'
805 + 'message' => __('Space not found', 'fluent-community'),
597 806 ]);
598 807 }
599 808
809 + // Lighter than formatSpaceData(), but listeners like fcom-chat need membership too.
600 810 $user = $this->getUser();
811 + $space->permissions = $space->getUserPermissions($user);
812 + $space->membership = $space->getMembership($user ? $user->ID : null);
601 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 +
602 866 $groups = Helper::getAllCommunityGroups($user, false);
603 867
604 868 foreach ($groups as $group) {
605 869 foreach ($group->spaces as $space) {
606 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 + }
607 876 }
608 877 }
609 878
610 - return [
611 - '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
612 897 ];
898 + return apply_filters('fluent_community/space_groups_api_response', $data, $request->all());
613 899 }
614 900
615 901 public function createSpaceGroup(Request $request)
616 902 {
617 - $user = $this->getUser(true);
618 - if (!$user->isCommunityModerator()) {
619 - return $this->sendError([
620 - 'message' => 'You are not allowed to create space group'
621 - ]);
622 - }
623 -
624 903 $data = $request->all();
625 904
626 905 $this->validate($data, [
627 906 'title' => 'required|unique:fcom_spaces,title',
@@ -627,13 +906,28 @@
627 906 'title' => 'required|unique:fcom_spaces,title',
628 907 'slug' => 'required|unique:fcom_spaces,slug'
629 908 ]);
630 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', ''));
631 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 +
632 926 $formattedData = [
633 - 'title' => sanitize_text_field($data['title']),
634 - 'slug' => sanitize_title($data['slug']),
635 - 'description' => sanitize_textarea_field($data['description']),
927 + 'title' => $title,
928 + 'slug' => $slug,
929 + 'description' => $desc,
636 930 'status' => 'active',
637 931 'type' => 'space_group',
638 932 'settings' => [
639 933 'always_show_spaces' => Arr::get($data, 'settings.always_show_spaces', 'yes'),
@@ -650,15 +944,8 @@
650 944 }
651 945
652 946 public function updateSpaceGroup(Request $request, $groupId)
653 947 {
654 - $user = $this->getUser();
655 - if (!$user || !$user->isCommunityModerator()) {
656 - return $this->sendError([
657 - 'message' => 'You are not allowed to create space group'
658 - ]);
659 - }
660 -
661 948 $group = SpaceGroup::findOrFail($groupId);
662 949 $data = $request->all();
663 950
664 951 $this->validate($data, [
@@ -670,9 +957,9 @@
670 957 ->first();
671 958
672 959 if ($taken) {
673 960 return $this->sendError([
674 - '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')
675 962 ]);
676 963 }
677 964
678 965 $formattedData = [
@@ -687,9 +974,9 @@
687 974
688 975 $group->fill($formattedData)->save();
689 976
690 977 return [
691 - 'message' => __('Space group has been created updated', 'fluent-community'),
978 + 'message' => __('Space group has been updated', 'fluent-community'),
692 979 'group' => $group
693 980 ];
694 981 }
695 982
@@ -694,21 +981,13 @@
694 981 }
695 982
696 983 public function deleteSpaceGroup(Request $request, $groupId)
697 984 {
698 -
699 - $user = $this->getUser();
700 - if (!$user || !$user->isCommunityModerator()) {
701 - return $this->sendError([
702 - 'message' => 'You are not allowed to create space group'
703 - ]);
704 - }
705 -
706 985 $group = SpaceGroup::findOrFail($groupId);
707 986
708 987 if (!$group->spaces->isEmpty()) {
709 988 return $this->sendError([
710 - 'message' => 'You can not delete this group. It has spaces'
989 + 'message' => __('You cannot delete this group. It has spaces', 'fluent-community')
711 990 ]);
712 991 }
713 992
714 993 $group->delete();
@@ -729,9 +1008,9 @@
729 1008 ]);
730 1009 }
731 1010
732 1011 return [
733 - 'message' => __('Space group indexes has been updated', 'fluent-community')
1012 + 'message' => __('Space group indexes have been updated.', 'fluent-community')
734 1013 ];
735 1014
736 1015 }
737 1016
@@ -746,9 +1025,9 @@
746 1025 ]);
747 1026 }
748 1027
749 1028 return [
750 - 'message' => __('Space indexes has been updated', 'fluent-community')
1029 + 'message' => __('Space indexes have been updated.', 'fluent-community')
751 1030 ];
752 1031 }
753 1032
754 1033 public function moveSpace(Request $request)
@@ -759,9 +1038,9 @@
759 1038 $space = BaseSpace::withoutGlobalScopes()->findOrFail($spaceId);
760 1039 $group = SpaceGroup::findOrFail($groupId);
761 1040
762 1041 $space->update([
763 - 'parent_id' => $groupId
1042 + 'parent_id' => $group->id
764 1043 ]);
765 1044
766 1045 return [
767 1046 'message' => __('Space has been moved successfully', 'fluent-community')
@@ -769,28 +1048,58 @@
769 1048 }
770 1049
771 1050 public function getLockScreenSettings(Request $request, $spaceSlug)
772 1051 {
773 - $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 +
774 1063 $lockscreen = $space->getLockscreen();
775 1064
1065 + $lockscreen = apply_filters('fluent_community/get_lockscreen_settings', $lockscreen, $space);
1066 +
776 1067 return [
777 1068 'lockscreen' => $lockscreen
778 1069 ];
779 1070 }
780 1071
781 - public function updateLockscreenSettings(Request $request, $spaceSlug)
1072 + public function getMetaSettings(Request $request, $spaceSlug)
782 1073 {
783 1074 $space = Space::where('slug', $spaceSlug)->firstOrFail();
1075 + $metaSettings = apply_filters('fluent_community/space/meta_fields', [], $space);
784 1076
785 - $settingFields = $request->get('lockscreen', []);
1077 + if (!$metaSettings) {
1078 + return [
1079 + 'meta_settings' => null
1080 + ];
1081 + }
786 1082
787 - $formattedFields = LockscreenService::formatLockscreenFields($settingFields);
1083 + return [
1084 + 'meta_settings' => $metaSettings
1085 + ];
1086 + }
788 1087
789 - $space->setLockscreen($formattedFields);
1088 + private function getActiveMemberCounts($spaceIds)
1089 + {
1090 + if (!$spaceIds) {
1091 + return [];
1092 + }
790 1093
791 - return [
792 - 'message' => 'Lockscreen settings has been updated successfully.',
793 - 'community' => $space
794 - ];
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();
795 1104 }
796 1105 }