PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.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 1.1.0 All 77 releases
← All changes | app/Http/Controllers/SpaceController.php +381 -166 1.1.02.10.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,10 @@
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\Functions\Utility;
15 13 use FluentCommunity\App\Services\ProfileHelper;
16 14 use FluentCommunity\Framework\Http\Request\Request;
17 15 use FluentCommunity\App\Models\Comment;
18 16 use FluentCommunity\App\Models\Reaction;
@@ -28,57 +26,65 @@
28 26 $q->where('user_id', get_current_user_id());
29 27 })
30 28 ->get();
31 29
32 - return [
30 + $data = [
33 31 'spaces' => $spaces
34 32 ];
33 +
34 + return apply_filters('fluent_community/spaces_api_response', $data, $this->request->all());
35 35 }
36 36
37 37 public function create(Request $request)
38 38 {
39 39 $currentUser = $this->getUser(true);
40 - $currentUser->verifyCommunityPermission('community_admin');
41 40
42 41 $data = $request->get('space', []);
43 42
44 - if (empty($data['slug'])) {
45 - $data['slug'] = sanitize_title($data['title'], '');
46 - } else {
47 - $data['slug'] = sanitize_title($data['slug'], '');
48 - }
43 + $slug = Arr::get($data, 'slug') ?: Arr::get($data, 'title');
49 44
50 - $data['title'] = sanitize_text_field($data['title']);
51 - $data['privacy'] = sanitize_text_field($data['privacy']);
45 + $slug = preg_replace('/[^a-zA-Z0-9-_]/', '', $slug);
52 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 +
53 51 $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')
52 + 'title' => 'required',
53 + 'slug' => 'unique:fcom_spaces,slug',
54 + 'privacy' => 'required|in:public,private,secret'
60 55 ]);
61 56
62 - $spaceGroup = SpaceGroup::findOrFail($data['parent_id']);
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 + }
63 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 +
64 79 $spaceData = apply_filters('fluent_community/space/create_data', [
65 80 'title' => sanitize_text_field($data['title']),
66 81 'slug' => $data['slug'],
67 82 '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 - 'hide_members_count' => Arr::get($data, 'settings.hide_members_count', 'no') == 'yes' ? 'yes' : 'no',
78 - ],
79 - 'parent_id' => $spaceGroup->id,
80 - 'serial' => BaseSpace::where('parent_id', $spaceGroup->id)->max('serial') + 1
83 + 'description' => sanitize_textarea_field(Arr::get($data, 'description', '')),
84 + 'settings' => $settings,
85 + 'parent_id' => $spaceGroup ? $spaceGroup->id : null,
86 + 'serial' => $serial ?: 1
81 87 ]);
82 88
83 89 $ogImage = Arr::get($data, 'settings.og_image', '');
84 90 $ogMedia = null;
@@ -124,8 +130,15 @@
124 130 $space->members()->attach(get_current_user_id(), [
125 131 'role' => 'admin'
126 132 ]);
127 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 +
128 141 $currentUser->cacheAccessSpaces();
129 142 do_action('fluent_community/space/created', $space, $data);
130 143
131 144 return [
@@ -135,64 +148,130 @@
135 148 }
136 149
137 150 public function discover(Request $request)
138 151 {
139 -
152 + $start = microtime(true);
140 153 $currentUser = $this->getUser();
154 + $type = $request->getSafe('type');
155 + $search = $request->getSafe('search');
156 + $sortBy = $request->getSafe('sort_by', 'sanitize_text_field', 'alphabetical');
141 157
142 - $spaces = Space::orderBy('title', 'ASC')
143 - ->with(['space_pivot' => function ($q) {
144 - $q->where('user_id', get_current_user_id());
145 - }])
158 + $spaces = Space::with(['space_pivot' => function ($q) {
159 + $q->where('user_id', get_current_user_id());
160 + }])
146 161 ->where(function ($q) {
147 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) {
148 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');
149 222 })
150 223 ->orWhereIn('privacy', ['public', 'private']);
151 - })
152 - ->get();
224 + });
225 + }
226 + $spaces = $spacesQuery->paginate();
153 227
228 + $memberCounts = $this->getActiveMemberCounts($spaces->pluck('id')->toArray());
229 +
154 230 foreach ($spaces as $space) {
155 - if (Arr::get($space->settings, 'hide_members_count') == 'yes' && (!$currentUser || !$space->verifyUserPermisson($currentUser, 'can_view_members', false))) {
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) {
156 235 $space->members_count = 0;
157 236 continue;
158 237 }
159 238
160 - $space->members_count = $space->members()->wherePivot('status', 'active')->count();
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]);
161 243 }
162 244
163 - return [
245 + return apply_filters('fluent_community/all_spaces_api_response', [
164 246 'spaces' => $spaces
165 - ];
247 + ], $request->all());
166 248 }
167 249
168 250 public function getBySlug(Request $request, $spaceSlug)
169 251 {
170 252 $user = $this->getUser();
171 - $space = Space::where('slug', $spaceSlug)
172 - ->firstOrFail();
253 + $space = Space::where('slug', $spaceSlug)->first();
173 254
174 - $space->permissions = $space->getUserPermissions($user);
175 - $space->description_rendered = FeedsHelper::mdToHtml($space->description);
176 - $space->membership = $space->getMembership(get_current_user_id());
177 - $space->topics = Utility::getTopicsBySpaceId($space->id);
255 + $userId = $user ? $user->ID : null;
178 256
179 - if (!Helper::isSiteAdmin()) {
180 - $space->lockscreen_config = LockscreenService::getLockscreenConfig($space, $space->membership);
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);
181 263 }
182 264
183 - if ($space->privacy == 'secret' && !$space->membership) {
184 - return $this->sendError([
185 - 'message' => __('You are not allowed to view this space', 'fluent-community'),
186 - 'error_type' => 'restricted'
187 - ]);
188 - }
265 + $space = $space->formatSpaceData($user);
189 266
190 267 do_action_ref_array('fluent_community/space', [&$space]);
191 268
192 - return [
269 + $data = [
193 270 'space' => $space
194 271 ];
272 +
273 + return apply_filters('fluent_community/space_api_response', $data, $request->all());
195 274 }
196 275
197 276 public function patchBySlug(Request $request, $slug)
198 277 {
@@ -200,42 +279,57 @@
200 279 ->first();
201 280
202 281 if (!$space) {
203 282 return $this->sendError([
204 - 'message' => 'Space not found'
283 + 'message' => __('Space not found', 'fluent-community')
205 284 ]);
206 285 }
207 286
208 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
287 + $data = $request->get('data', []);
209 288
210 - $data = $request->get('data', []);
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 + }
211 294
212 - if (!empty($data['title'])) {
213 - $taken = Space::where('title', $data['title'])
295 + if (!empty($data['slug'])) {
296 + $taken = Space::where('slug', $data['slug'])
214 297 ->where('id', '!=', $space->id)
215 298 ->first();
299 +
216 300 if ($taken) {
217 301 return $this->sendError([
218 - 'message' => 'Space title is already taken. Please use a different title'
302 + 'message' => __('Slug is already taken. Please use a different slug', 'fluent-community')
219 303 ]);
220 304 }
221 305 }
222 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 +
223 323 $mediaTypes = ['cover_photo', 'logo'];
224 324 foreach ($mediaTypes as $type) {
225 325 if (!empty($data[$type])) {
226 326 $media = Helper::getMediaFromUrl($data[$type]);
227 - if (!$media) {
327 + if (!$media || $media->is_active) {
228 328 unset($data[$type]);
229 329 continue;
230 330 }
231 331
232 - if (!$media || $media->is_active) {
233 - return $this->sendError([
234 - 'message' => 'Invalid media image. Please upload a new one.'
235 - ]);
236 - }
237 -
238 332 $data[$type] = $media->public_url;
239 333
240 334 $media->update([
241 335 'is_active' => true,
@@ -249,24 +343,53 @@
249 343 }
250 344
251 345 $data = apply_filters('fluent_community/space/update_data', $data, $space);
252 346
253 - $space->updateCustomData($data, true);
347 + if (isset($data['parent_id']) && !$data['parent_id']) {
348 + $data['parent_id'] = '';
349 + }
254 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 +
255 359 if (Arr::has($data, 'topic_ids')) {
360 + $topicsConfig = Helper::getTopicsConfig();
256 361 $topicIds = (array)Arr::get($data, 'topic_ids', []);
362 + $topicIds = array_slice($topicIds, 0, $topicsConfig['max_topics_per_space']);
257 363 $space->syncTopics($topicIds);
258 364 }
259 365
260 366 do_action('fluent_community/space/updated', $space, $data);
367 + $slugUpdated = $slug != $space->slug;
261 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 +
262 376 return [
263 - 'message' => __('Space has been updated', 'fluent-community')
377 + 'message' => __('Settings have been updated', 'fluent-community'),
378 + 'redirect_url' => $slugUpdated ? $space->getPermalink() : ''
264 379 ];
265 380 }
266 381
382 + public function patchById(Request $request, $id)
383 + {
384 + $space = Space::findOrFail($id);
385 +
386 + return $this->patchBySlug($request, $space->slug);
387 + }
388 +
267 389 public function getMembers(Request $request, $slug)
268 390 {
391 + /** @var Space $space */
269 392 $space = Space::where('slug', $slug)
270 393 ->firstOrFail();
271 394
272 395 $user = $this->getUser();
@@ -276,8 +399,9 @@
276 399 'message' => __('You are not allowed to view members of this space', 'fluent-community'),
277 400 'permission_failed' => true
278 401 ]);
279 402 }
403 +
280 404 $search = $request->getSafe('search', 'sanitize_text_field');
281 405
282 406 $pendingCount = 0;
283 407 if ($user && $user->can('can_add_member', $space)) {
@@ -296,30 +420,48 @@
296 420 }])
297 421 ->where('status', 'pending')
298 422 ->paginate();
299 423
300 - return [
424 + return apply_filters('fluent_community/space_members_api_response', [
301 425 'members' => $pendingRequests,
302 426 'pending_count' => $pendingCount
303 - ];
427 + ], $pendingRequests, $request->all());
304 428 }
305 429 }
306 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 +
307 445 $spaceMembers = SpaceUserPivot::bySpace($space->id)
308 446 ->whereHas('xprofile', function ($q) use ($search) {
309 - return $q->searchBy($search)
310 - ->where('status', 'active');
447 + $q->searchBy($search)->where('status', 'active');
311 448 })
449 + ->where('fcom_space_user.status', 'active')
312 450 ->with(['xprofile' => function ($q) {
313 451 $q->select(ProfileHelper::getXProfilePublicFields());
314 452 }])
315 - ->where('status', 'active')
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)
316 458 ->paginate();
317 459
318 - return [
460 + return apply_filters('fluent_community/space_members_api_response', [
319 461 'members' => $spaceMembers,
320 462 'pending_count' => $pendingCount
321 - ];
463 + ], $spaceMembers, $request->all());
322 464 }
323 465
324 466 public function join(Request $request, $slug)
325 467 {
@@ -326,9 +468,9 @@
326 468 $space = Space::where('slug', $slug)->first();
327 469
328 470 if (!$space) {
329 471 return $this->sendError([
330 - 'message' => 'Space not found'
472 + 'message' => __('Space not found', 'fluent-community')
331 473 ]);
332 474 }
333 475
334 476 $user = $this->getUser();
@@ -336,22 +478,23 @@
336 478 $membership = $space->getMembership(get_current_user_id());
337 479
338 480 if ($membership) {
339 481 return $this->sendError([
340 - 'message' => 'You are already a member of this space. Please reload this page'
482 + 'message' => __('You are already a member of this space. Please reload this page', 'fluent-community')
341 483 ]);
342 484 }
343 485
344 486 $roles = $user->getCommunityRoles();
487 + $isPrivileged = !!array_intersect($roles, ['admin', 'moderator']);
345 488
346 - if (!$roles && $space->privacy == 'secret') {
489 + if (!$isPrivileged && $space->privacy == 'secret') {
347 490 return $this->sendError([
348 - 'message' => 'You are not allowed to join this space'
491 + 'message' => __('You are not allowed to join this space', 'fluent-community')
349 492 ]);
350 493 }
351 494
352 495 $status = 'active';
353 - if (!$roles) {
496 + if (!$isPrivileged) {
354 497 if ($space->privacy != 'public') {
355 498 $status = apply_filters('fluent_community/space/join_status_for_private', 'pending', $space, $user);
356 499
357 500 if (!in_array($status, ['pending', 'active'])) {
@@ -391,9 +534,9 @@
391 534 $space = Space::where('slug', $slug)->first();
392 535
393 536 if (!$space) {
394 537 return $this->sendError([
395 - 'message' => 'Space not found'
538 + 'message' => __('Space not found', 'fluent-community')
396 539 ]);
397 540 }
398 541
399 542 $membership = $space->getMembership($user->ID);
@@ -399,9 +542,9 @@
399 542 $membership = $space->getMembership($user->ID);
400 543
401 544 if (!$membership) {
402 545 return $this->sendError([
403 - 'message' => 'You are not a member of this community'
546 + 'message' => __('You are not a member of this community', 'fluent-community')
404 547 ]);
405 548 }
406 549
407 550 Helper::removeFromSpace($space, $user->ID, 'self');
@@ -410,24 +553,18 @@
410 553 'message' => __('You have left this space', 'fluent-community')
411 554 ];
412 555 }
413 556
414 - public function delete(Request $request, $slug)
557 + public function deleteBySlug(Request $request, $slug)
415 558 {
416 559 $space = Space::where('slug', $slug)->first();
417 560
418 561 if (!$space) {
419 562 return $this->sendError([
420 - 'message' => 'Space not found'
563 + 'message' => __('Space not found', 'fluent-community')
421 564 ]);
422 565 }
423 566
424 - if (!Helper::isSiteAdmin()) {
425 - return $this->sendError([
426 - 'message' => 'You are not allowed to delete this community'
427 - ]);
428 - }
429 -
430 567 do_action('fluent_community/space/before_delete', $space);
431 568
432 569 Comment::whereHas('post', function ($q) use ($space) {
433 570 $q->where('space_id', $space->id);
@@ -450,8 +587,15 @@
450 587 'message' => __('Space has been deleted successfully', 'fluent-community')
451 588 ];
452 589 }
453 590
591 + public function deleteById(Request $request, $id)
592 + {
593 + $space = Space::findOrFail($id);
594 +
595 + return $this->deleteBySlug($request, $space->slug);
596 + }
597 +
454 598 public function addMember(Request $request, $slug)
455 599 {
456 600 $space = Space::where('slug', $slug)->first();
457 601
@@ -456,9 +600,9 @@
456 600 $space = Space::where('slug', $slug)->first();
457 601
458 602 if (!$space) {
459 603 return $this->sendError([
460 - 'message' => 'Space not found'
604 + 'message' => __('Space not found', 'fluent-community')
461 605 ]);
462 606 }
463 607
464 608 $this->validate($request->all(), [
@@ -466,9 +610,10 @@
466 610 ]);
467 611
468 612 $userId = $request->get('user_id');
469 613 $targetUser = User::findOrFail($userId);
470 - $xprofile = $targetUser->syncXProfile();
614 + $targetUser->syncXProfile();
615 + $xprofile = $targetUser->xprofile;
471 616
472 617 if ($xprofile && $xprofile->status != 'active') {
473 618 return $this->sendError([
474 619 'message' => __('Selected user is not active', 'fluent-community')
@@ -474,16 +619,15 @@
474 619 'message' => __('Selected user is not active', 'fluent-community')
475 620 ]);
476 621 }
477 622
478 - $admin = User::find(get_current_user_id());
479 - $admin->verifySpacePermission('can_add_member', $space);
480 -
481 623 $pivot = SpaceUserPivot::bySpace($space->id)
482 624 ->byUser($userId)
483 625 ->first();
484 626
485 - $role = $request->get('role', 'member');
627 + $allowedRoles = ['member', 'moderator', 'admin'];
628 + $requestedRole = $request->get('role', 'member');
629 + $role = in_array($requestedRole, $allowedRoles, true) ? $requestedRole : 'member';
486 630
487 631 if ($pivot) {
488 632 if ($pivot->status == 'active') {
489 633 if ($role != $pivot->role) {
@@ -492,14 +636,14 @@
492 636
493 637 do_action('fluent_community/space/member/role_updated', $space, $pivot);
494 638
495 639 return [
496 - 'message' => 'Member role updated'
640 + 'message' => __('Member role updated', 'fluent-community')
497 641 ];
498 642 }
499 643
500 644 return $this->sendError([
501 - 'message' => 'Selected user is already a member of this community'
645 + 'message' => __('Selected user is already a member of this community', 'fluent-community')
502 646 ]);
503 647 }
504 648
505 649 $pivot->status = 'active';
@@ -510,9 +654,9 @@
510 654 do_action('fluent_community/space/member/role_updated', $space, $pivot);
511 655 }
512 656
513 657 return [
514 - 'message' => 'Member approved'
658 + 'message' => __('Member approved', 'fluent-community')
515 659 ];
516 660 }
517 661
518 662 $space->members()->attach($userId, [
@@ -524,9 +668,9 @@
524 668
525 669 do_action('fluent_community/space/joined', $space, $userId, 'by_admin');
526 670
527 671 return [
528 - 'message' => 'User has been added to this community'
672 + 'message' => __('User has been added to this Space', 'fluent-community')
529 673 ];
530 674 }
531 675
532 676 public function removeMember(Request $request, $slug)
@@ -534,17 +678,14 @@
534 678 $space = Space::where('slug', $slug)->first();
535 679
536 680 if (!$space) {
537 681 return $this->sendError([
538 - 'message' => 'Space not found'
682 + 'message' => __('Space not found', 'fluent-community')
539 683 ]);
540 684 }
541 685
542 686 $userId = $request->get('user_id');
543 687
544 - $admin = User::find(get_current_user_id());
545 - $admin->verifySpacePermission('can_remove_member', $space);
546 -
547 688 $pivot = SpaceUserPivot::bySpace($space->id)
548 689 ->byUser($userId)
549 690 ->first();
550 691
@@ -549,9 +690,9 @@
549 690 ->first();
550 691
551 692 if (!$pivot) {
552 693 return $this->sendError([
553 - 'message' => 'Selected user is not a member of this community'
694 + 'message' => __('Selected user is not a member of this community', 'fluent-community')
554 695 ]);
555 696 }
556 697
557 698 $pivot->delete();
@@ -579,25 +720,53 @@
579 720
580 721 $isMod = $currentUser->isCommunityModerator() && current_user_can('list_users');
581 722
582 723 $spaceId = $request->get('space_id');
724 + $space = Space::findOrFail($spaceId);
583 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 +
584 732 $selects = ['ID', 'display_name'];
585 -
586 733 if ($isMod) {
587 734 $selects[] = 'user_email';
588 735 }
589 736
590 - $users = User::whereDoesntHave('spaces', function ($q) use ($spaceId) {
591 - return $q->where('space_id', $spaceId);
592 - })
593 - ->select($selects)
594 - ->searchBy($request->get('search'))
595 - ->paginate();
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'));
596 743
597 - return [
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 = [
598 766 'users' => $users
599 767 ];
768 + return apply_filters('fluent_community/space_non_members_api_response', $data, $request->all());
600 769 }
601 770
602 771 public function updateLinks(Request $request, $slug)
603 772 {
@@ -604,14 +773,12 @@
604 773 $space = Space::where('slug', $slug)->first();
605 774
606 775 if (!$space) {
607 776 return $this->sendError([
608 - 'message' => 'Space not found'
777 + 'message' => __('Space not found', 'fluent-community')
609 778 ]);
610 779 }
611 780
612 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
613 -
614 781 $links = $request->get('links', []);
615 782
616 783 $links = array_map(function ($link) {
617 784 return CustomSanitizer::santizeLinkItem($link);
@@ -629,13 +796,16 @@
629 796 }
630 797
631 798 public function getSpaceGroups(Request $request)
632 799 {
633 - $user = $this->getUser(true);
634 - if (!$user->isCommunityModerator()) {
635 - return $this->sendError([
636 - 'message' => 'You are not allowed to create space group'
637 - ]);
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 + ];
638 808 }
639 809
640 810 $user = $this->getUser();
641 811
@@ -643,25 +813,40 @@
643 813
644 814 foreach ($groups as $group) {
645 815 foreach ($group->spaces as $space) {
646 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 + }
647 822 }
648 823 }
649 824
650 - return [
651 - 'groups' => $groups
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
652 843 ];
844 + return apply_filters('fluent_community/space_groups_api_response', $data, $request->all());
653 845 }
654 846
655 847 public function createSpaceGroup(Request $request)
656 848 {
657 - $user = $this->getUser(true);
658 - if (!$user->isCommunityModerator()) {
659 - return $this->sendError([
660 - 'message' => 'You are not allowed to create space group'
661 - ]);
662 - }
663 -
664 849 $data = $request->all();
665 850
666 851 $this->validate($data, [
667 852 'title' => 'required|unique:fcom_spaces,title',
@@ -667,13 +852,28 @@
667 852 'title' => 'required|unique:fcom_spaces,title',
668 853 'slug' => 'required|unique:fcom_spaces,slug'
669 854 ]);
670 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', ''));
671 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 +
672 872 $formattedData = [
673 - 'title' => sanitize_text_field($data['title']),
674 - 'slug' => sanitize_title($data['slug']),
675 - 'description' => sanitize_textarea_field($data['description']),
873 + 'title' => $title,
874 + 'slug' => $slug,
875 + 'description' => $desc,
676 876 'status' => 'active',
677 877 'type' => 'space_group',
678 878 'settings' => [
679 879 'always_show_spaces' => Arr::get($data, 'settings.always_show_spaces', 'yes'),
@@ -690,15 +890,8 @@
690 890 }
691 891
692 892 public function updateSpaceGroup(Request $request, $groupId)
693 893 {
694 - $user = $this->getUser();
695 - if (!$user || !$user->isCommunityModerator()) {
696 - return $this->sendError([
697 - 'message' => 'You are not allowed to create space group'
698 - ]);
699 - }
700 -
701 894 $group = SpaceGroup::findOrFail($groupId);
702 895 $data = $request->all();
703 896
704 897 $this->validate($data, [
@@ -710,9 +903,9 @@
710 903 ->first();
711 904
712 905 if ($taken) {
713 906 return $this->sendError([
714 - 'message' => 'The title is already taken. Please use a different title'
907 + 'message' => __('The title is already taken. Please use a different title', 'fluent-community')
715 908 ]);
716 909 }
717 910
718 911 $formattedData = [
@@ -727,9 +920,9 @@
727 920
728 921 $group->fill($formattedData)->save();
729 922
730 923 return [
731 - 'message' => __('Space group has been created updated', 'fluent-community'),
924 + 'message' => __('Space group has been updated', 'fluent-community'),
732 925 'group' => $group
733 926 ];
734 927 }
735 928
@@ -734,21 +927,13 @@
734 927 }
735 928
736 929 public function deleteSpaceGroup(Request $request, $groupId)
737 930 {
738 -
739 - $user = $this->getUser();
740 - if (!$user || !$user->isCommunityModerator()) {
741 - return $this->sendError([
742 - 'message' => 'You are not allowed to create space group'
743 - ]);
744 - }
745 -
746 931 $group = SpaceGroup::findOrFail($groupId);
747 932
748 933 if (!$group->spaces->isEmpty()) {
749 934 return $this->sendError([
750 - 'message' => 'You can not delete this group. It has spaces'
935 + 'message' => __('You cannot delete this group. It has spaces', 'fluent-community')
751 936 ]);
752 937 }
753 938
754 939 $group->delete();
@@ -799,9 +984,9 @@
799 984 $space = BaseSpace::withoutGlobalScopes()->findOrFail($spaceId);
800 985 $group = SpaceGroup::findOrFail($groupId);
801 986
802 987 $space->update([
803 - 'parent_id' => $groupId
988 + 'parent_id' => $group->id
804 989 ]);
805 990
806 991 return [
807 992 'message' => __('Space has been moved successfully', 'fluent-community')
@@ -809,28 +994,58 @@
809 994 }
810 995
811 996 public function getLockScreenSettings(Request $request, $spaceSlug)
812 997 {
813 - $space = Space::where('slug', $spaceSlug)->firstOrFail();
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 +
814 1009 $lockscreen = $space->getLockscreen();
815 1010
1011 + $lockscreen = apply_filters('fluent_community/get_lockscreen_settings', $lockscreen, $space);
1012 +
816 1013 return [
817 1014 'lockscreen' => $lockscreen
818 1015 ];
819 1016 }
820 1017
821 - public function updateLockscreenSettings(Request $request, $spaceSlug)
1018 + public function getMetaSettings(Request $request, $spaceSlug)
822 1019 {
823 1020 $space = Space::where('slug', $spaceSlug)->firstOrFail();
1021 + $metaSettings = apply_filters('fluent_community/space/meta_fields', [], $space);
824 1022
825 - $settingFields = $request->get('lockscreen', []);
1023 + if (!$metaSettings) {
1024 + return [
1025 + 'meta_settings' => null
1026 + ];
1027 + }
826 1028
827 - $formattedFields = LockscreenService::formatLockscreenFields($settingFields);
1029 + return [
1030 + 'meta_settings' => $metaSettings
1031 + ];
1032 + }
828 1033
829 - $space->setLockscreen($formattedFields);
1034 + private function getActiveMemberCounts($spaceIds)
1035 + {
1036 + if (!$spaceIds) {
1037 + return [];
1038 + }
830 1039
831 - return [
832 - 'message' => 'Lockscreen settings have been updated successfully.',
833 - 'community' => $space
834 - ];
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();
835 1050 }
836 1051 }