PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
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 +410 -167 1.0.952.10.01 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,59 +26,85 @@
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 - ],
78 - 'parent_id' => $spaceGroup->id,
79 - '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
80 87 ]);
81 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 +
82 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 + }
83 107
84 108 $imageTypes = ['cover_photo', 'logo'];
85 109 $metaData = [];
86 110 foreach ($imageTypes as $type) {
@@ -106,8 +130,15 @@
106 130 $space->members()->attach(get_current_user_id(), [
107 131 'role' => 'admin'
108 132 ]);
109 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 +
110 141 $currentUser->cacheAccessSpaces();
111 142 do_action('fluent_community/space/created', $space, $data);
112 143
113 144 return [
@@ -117,56 +148,130 @@
117 148 }
118 149
119 150 public function discover(Request $request)
120 151 {
121 - $spaces = Space::orderBy('title', 'ASC')
122 - ->with(['space_pivot' => function ($q) {
123 - $q->where('user_id', get_current_user_id());
124 - }])
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 + }])
125 161 ->where(function ($q) {
126 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) {
127 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');
128 222 })
129 223 ->orWhereIn('privacy', ['public', 'private']);
130 - })
131 - ->get();
224 + });
225 + }
226 + $spaces = $spacesQuery->paginate();
132 227
228 + $memberCounts = $this->getActiveMemberCounts($spaces->pluck('id')->toArray());
229 +
133 230 foreach ($spaces as $space) {
134 - $space->members_count = $space->members()->wherePivot('status', 'active')->count();
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]);
135 243 }
136 244
137 - return [
245 + return apply_filters('fluent_community/all_spaces_api_response', [
138 246 'spaces' => $spaces
139 - ];
247 + ], $request->all());
140 248 }
141 249
142 250 public function getBySlug(Request $request, $spaceSlug)
143 251 {
144 252 $user = $this->getUser();
145 - $space = Space::where('slug', $spaceSlug)
146 - ->firstOrFail();
253 + $space = Space::where('slug', $spaceSlug)->first();
147 254
148 - $space->permissions = $space->getUserPermissions($user);
149 - $space->description_rendered = FeedsHelper::mdToHtml($space->description);
150 - $space->membership = $space->getMembership(get_current_user_id());
151 - $space->topics = Utility::getTopicsBySpaceId($space->id);
255 + $userId = $user ? $user->ID : null;
152 256
153 - if (!Helper::isSiteAdmin()) {
154 - $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);
155 263 }
156 264
157 - if ($space->privacy == 'secret' && !$space->membership) {
158 - return $this->sendError([
159 - 'message' => __('You are not allowed to view this space', 'fluent-community'),
160 - 'error_type' => 'restricted'
161 - ]);
162 - }
265 + $space = $space->formatSpaceData($user);
163 266
164 267 do_action_ref_array('fluent_community/space', [&$space]);
165 268
166 - return [
269 + $data = [
167 270 'space' => $space
168 271 ];
272 +
273 + return apply_filters('fluent_community/space_api_response', $data, $request->all());
169 274 }
170 275
171 276 public function patchBySlug(Request $request, $slug)
172 277 {
@@ -174,42 +279,57 @@
174 279 ->first();
175 280
176 281 if (!$space) {
177 282 return $this->sendError([
178 - 'message' => 'Space not found'
283 + 'message' => __('Space not found', 'fluent-community')
179 284 ]);
180 285 }
181 286
182 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
287 + $data = $request->get('data', []);
183 288
184 - $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 + }
185 294
186 - if (!empty($data['title'])) {
187 - $taken = Space::where('title', $data['title'])
295 + if (!empty($data['slug'])) {
296 + $taken = Space::where('slug', $data['slug'])
188 297 ->where('id', '!=', $space->id)
189 298 ->first();
299 +
190 300 if ($taken) {
191 301 return $this->sendError([
192 - 'message' => 'Space title is already taken. Please use a different title'
302 + 'message' => __('Slug is already taken. Please use a different slug', 'fluent-community')
193 303 ]);
194 304 }
195 305 }
196 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 +
197 323 $mediaTypes = ['cover_photo', 'logo'];
198 324 foreach ($mediaTypes as $type) {
199 325 if (!empty($data[$type])) {
200 326 $media = Helper::getMediaFromUrl($data[$type]);
201 - if (!$media) {
327 + if (!$media || $media->is_active) {
202 328 unset($data[$type]);
203 329 continue;
204 330 }
205 331
206 - if (!$media || $media->is_active) {
207 - return $this->sendError([
208 - 'message' => 'Invalid media image. Please upload a new one.'
209 - ]);
210 - }
211 -
212 332 $data[$type] = $media->public_url;
213 333
214 334 $media->update([
215 335 'is_active' => true,
@@ -223,22 +343,53 @@
223 343 }
224 344
225 345 $data = apply_filters('fluent_community/space/update_data', $data, $space);
226 346
227 - $space->updateCustomData($data, true);
347 + if (isset($data['parent_id']) && !$data['parent_id']) {
348 + $data['parent_id'] = '';
349 + }
228 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 +
229 359 if (Arr::has($data, 'topic_ids')) {
360 + $topicsConfig = Helper::getTopicsConfig();
230 361 $topicIds = (array)Arr::get($data, 'topic_ids', []);
362 + $topicIds = array_slice($topicIds, 0, $topicsConfig['max_topics_per_space']);
231 363 $space->syncTopics($topicIds);
232 364 }
233 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 +
234 376 return [
235 - 'message' => __('Space has been updated', 'fluent-community')
377 + 'message' => __('Settings have been updated', 'fluent-community'),
378 + 'redirect_url' => $slugUpdated ? $space->getPermalink() : ''
236 379 ];
237 380 }
238 381
382 + public function patchById(Request $request, $id)
383 + {
384 + $space = Space::findOrFail($id);
385 +
386 + return $this->patchBySlug($request, $space->slug);
387 + }
388 +
239 389 public function getMembers(Request $request, $slug)
240 390 {
391 + /** @var Space $space */
241 392 $space = Space::where('slug', $slug)
242 393 ->firstOrFail();
243 394
244 395 $user = $this->getUser();
@@ -248,8 +399,9 @@
248 399 'message' => __('You are not allowed to view members of this space', 'fluent-community'),
249 400 'permission_failed' => true
250 401 ]);
251 402 }
403 +
252 404 $search = $request->getSafe('search', 'sanitize_text_field');
253 405
254 406 $pendingCount = 0;
255 407 if ($user && $user->can('can_add_member', $space)) {
@@ -268,30 +420,48 @@
268 420 }])
269 421 ->where('status', 'pending')
270 422 ->paginate();
271 423
272 - return [
424 + return apply_filters('fluent_community/space_members_api_response', [
273 425 'members' => $pendingRequests,
274 426 'pending_count' => $pendingCount
275 - ];
427 + ], $pendingRequests, $request->all());
276 428 }
277 429 }
278 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 +
279 445 $spaceMembers = SpaceUserPivot::bySpace($space->id)
280 446 ->whereHas('xprofile', function ($q) use ($search) {
281 - return $q->searchBy($search)
282 - ->where('status', 'active');
447 + $q->searchBy($search)->where('status', 'active');
283 448 })
449 + ->where('fcom_space_user.status', 'active')
284 450 ->with(['xprofile' => function ($q) {
285 451 $q->select(ProfileHelper::getXProfilePublicFields());
286 452 }])
287 - ->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)
288 458 ->paginate();
289 459
290 - return [
460 + return apply_filters('fluent_community/space_members_api_response', [
291 461 'members' => $spaceMembers,
292 462 'pending_count' => $pendingCount
293 - ];
463 + ], $spaceMembers, $request->all());
294 464 }
295 465
296 466 public function join(Request $request, $slug)
297 467 {
@@ -298,9 +468,9 @@
298 468 $space = Space::where('slug', $slug)->first();
299 469
300 470 if (!$space) {
301 471 return $this->sendError([
302 - 'message' => 'Space not found'
472 + 'message' => __('Space not found', 'fluent-community')
303 473 ]);
304 474 }
305 475
306 476 $user = $this->getUser();
@@ -308,22 +478,23 @@
308 478 $membership = $space->getMembership(get_current_user_id());
309 479
310 480 if ($membership) {
311 481 return $this->sendError([
312 - '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')
313 483 ]);
314 484 }
315 485
316 486 $roles = $user->getCommunityRoles();
487 + $isPrivileged = !!array_intersect($roles, ['admin', 'moderator']);
317 488
318 - if (!$roles && $space->privacy == 'secret') {
489 + if (!$isPrivileged && $space->privacy == 'secret') {
319 490 return $this->sendError([
320 - 'message' => 'You are not allowed to join this space'
491 + 'message' => __('You are not allowed to join this space', 'fluent-community')
321 492 ]);
322 493 }
323 494
324 495 $status = 'active';
325 - if (!$roles) {
496 + if (!$isPrivileged) {
326 497 if ($space->privacy != 'public') {
327 498 $status = apply_filters('fluent_community/space/join_status_for_private', 'pending', $space, $user);
328 499
329 500 if (!in_array($status, ['pending', 'active'])) {
@@ -351,9 +522,9 @@
351 522
352 523 $user->cacheAccessSpaces();
353 524
354 525 return [
355 - 'message' => ($status == 'active') ? __('You have joined this Space', 'fluent-community') : __('Your join request has been sent to the Space admin', 'fluent-community'),
526 + 'message' => ($status == 'active') ? __('You have joined this Space', 'fluent-community') : __('Your join request has been sent to the Space admin.', 'fluent-community'),
356 527 'membership' => $space->membership
357 528 ];
358 529 }
359 530
@@ -363,9 +534,9 @@
363 534 $space = Space::where('slug', $slug)->first();
364 535
365 536 if (!$space) {
366 537 return $this->sendError([
367 - 'message' => 'Space not found'
538 + 'message' => __('Space not found', 'fluent-community')
368 539 ]);
369 540 }
370 541
371 542 $membership = $space->getMembership($user->ID);
@@ -371,9 +542,9 @@
371 542 $membership = $space->getMembership($user->ID);
372 543
373 544 if (!$membership) {
374 545 return $this->sendError([
375 - 'message' => 'You are not a member of this community'
546 + 'message' => __('You are not a member of this community', 'fluent-community')
376 547 ]);
377 548 }
378 549
379 550 Helper::removeFromSpace($space, $user->ID, 'self');
@@ -382,24 +553,18 @@
382 553 'message' => __('You have left this space', 'fluent-community')
383 554 ];
384 555 }
385 556
386 - public function delete(Request $request, $slug)
557 + public function deleteBySlug(Request $request, $slug)
387 558 {
388 559 $space = Space::where('slug', $slug)->first();
389 560
390 561 if (!$space) {
391 562 return $this->sendError([
392 - 'message' => 'Space not found'
563 + 'message' => __('Space not found', 'fluent-community')
393 564 ]);
394 565 }
395 566
396 - if (!Helper::isSiteAdmin()) {
397 - return $this->sendError([
398 - 'message' => 'You are not allowed to delete this community'
399 - ]);
400 - }
401 -
402 567 do_action('fluent_community/space/before_delete', $space);
403 568
404 569 Comment::whereHas('post', function ($q) use ($space) {
405 570 $q->where('space_id', $space->id);
@@ -422,8 +587,15 @@
422 587 'message' => __('Space has been deleted successfully', 'fluent-community')
423 588 ];
424 589 }
425 590
591 + public function deleteById(Request $request, $id)
592 + {
593 + $space = Space::findOrFail($id);
594 +
595 + return $this->deleteBySlug($request, $space->slug);
596 + }
597 +
426 598 public function addMember(Request $request, $slug)
427 599 {
428 600 $space = Space::where('slug', $slug)->first();
429 601
@@ -428,9 +600,9 @@
428 600 $space = Space::where('slug', $slug)->first();
429 601
430 602 if (!$space) {
431 603 return $this->sendError([
432 - 'message' => 'Space not found'
604 + 'message' => __('Space not found', 'fluent-community')
433 605 ]);
434 606 }
435 607
436 608 $this->validate($request->all(), [
@@ -438,9 +610,10 @@
438 610 ]);
439 611
440 612 $userId = $request->get('user_id');
441 613 $targetUser = User::findOrFail($userId);
442 - $xprofile = $targetUser->syncXProfile();
614 + $targetUser->syncXProfile();
615 + $xprofile = $targetUser->xprofile;
443 616
444 617 if ($xprofile && $xprofile->status != 'active') {
445 618 return $this->sendError([
446 619 'message' => __('Selected user is not active', 'fluent-community')
@@ -446,16 +619,15 @@
446 619 'message' => __('Selected user is not active', 'fluent-community')
447 620 ]);
448 621 }
449 622
450 - $admin = User::find(get_current_user_id());
451 - $admin->verifySpacePermission('can_add_member', $space);
452 -
453 623 $pivot = SpaceUserPivot::bySpace($space->id)
454 624 ->byUser($userId)
455 625 ->first();
456 626
457 - $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';
458 630
459 631 if ($pivot) {
460 632 if ($pivot->status == 'active') {
461 633 if ($role != $pivot->role) {
@@ -464,14 +636,14 @@
464 636
465 637 do_action('fluent_community/space/member/role_updated', $space, $pivot);
466 638
467 639 return [
468 - 'message' => 'Member role updated'
640 + 'message' => __('Member role updated', 'fluent-community')
469 641 ];
470 642 }
471 643
472 644 return $this->sendError([
473 - 'message' => 'Selected user is already a member of this community'
645 + 'message' => __('Selected user is already a member of this community', 'fluent-community')
474 646 ]);
475 647 }
476 648
477 649 $pivot->status = 'active';
@@ -482,9 +654,9 @@
482 654 do_action('fluent_community/space/member/role_updated', $space, $pivot);
483 655 }
484 656
485 657 return [
486 - 'message' => 'Member approved'
658 + 'message' => __('Member approved', 'fluent-community')
487 659 ];
488 660 }
489 661
490 662 $space->members()->attach($userId, [
@@ -496,9 +668,9 @@
496 668
497 669 do_action('fluent_community/space/joined', $space, $userId, 'by_admin');
498 670
499 671 return [
500 - 'message' => 'User has been added to this community'
672 + 'message' => __('User has been added to this Space', 'fluent-community')
501 673 ];
502 674 }
503 675
504 676 public function removeMember(Request $request, $slug)
@@ -506,17 +678,14 @@
506 678 $space = Space::where('slug', $slug)->first();
507 679
508 680 if (!$space) {
509 681 return $this->sendError([
510 - 'message' => 'Space not found'
682 + 'message' => __('Space not found', 'fluent-community')
511 683 ]);
512 684 }
513 685
514 686 $userId = $request->get('user_id');
515 687
516 - $admin = User::find(get_current_user_id());
517 - $admin->verifySpacePermission('can_remove_member', $space);
518 -
519 688 $pivot = SpaceUserPivot::bySpace($space->id)
520 689 ->byUser($userId)
521 690 ->first();
522 691
@@ -521,9 +690,9 @@
521 690 ->first();
522 691
523 692 if (!$pivot) {
524 693 return $this->sendError([
525 - 'message' => 'Selected user is not a member of this community'
694 + 'message' => __('Selected user is not a member of this community', 'fluent-community')
526 695 ]);
527 696 }
528 697
529 698 $pivot->delete();
@@ -551,25 +720,53 @@
551 720
552 721 $isMod = $currentUser->isCommunityModerator() && current_user_can('list_users');
553 722
554 723 $spaceId = $request->get('space_id');
724 + $space = Space::findOrFail($spaceId);
555 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 +
556 732 $selects = ['ID', 'display_name'];
557 -
558 733 if ($isMod) {
559 734 $selects[] = 'user_email';
560 735 }
561 736
562 - $users = User::whereDoesntHave('spaces', function ($q) use ($spaceId) {
563 - return $q->where('space_id', $spaceId);
564 - })
565 - ->select($selects)
566 - ->searchBy($request->get('search'))
567 - ->paginate();
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'));
568 743
569 - 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 = [
570 766 'users' => $users
571 767 ];
768 + return apply_filters('fluent_community/space_non_members_api_response', $data, $request->all());
572 769 }
573 770
574 771 public function updateLinks(Request $request, $slug)
575 772 {
@@ -576,14 +773,12 @@
576 773 $space = Space::where('slug', $slug)->first();
577 774
578 775 if (!$space) {
579 776 return $this->sendError([
580 - 'message' => 'Space not found'
777 + 'message' => __('Space not found', 'fluent-community')
581 778 ]);
582 779 }
583 780
584 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
585 -
586 781 $links = $request->get('links', []);
587 782
588 783 $links = array_map(function ($link) {
589 784 return CustomSanitizer::santizeLinkItem($link);
@@ -594,9 +789,9 @@
594 789 $space->settings = $settings;
595 790 $space->save();
596 791
597 792 return [
598 - 'message' => __('Links has been updated for the space', 'fluent-community'),
793 + 'message' => __('Links have been updated for the space', 'fluent-community'),
599 794 'links' => $links
600 795 ];
601 796 }
602 797
@@ -601,13 +796,16 @@
601 796 }
602 797
603 798 public function getSpaceGroups(Request $request)
604 799 {
605 - $user = $this->getUser(true);
606 - if (!$user->isCommunityModerator()) {
607 - return $this->sendError([
608 - 'message' => 'You are not allowed to create space group'
609 - ]);
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 + ];
610 808 }
611 809
612 810 $user = $this->getUser();
613 811
@@ -615,25 +813,40 @@
615 813
616 814 foreach ($groups as $group) {
617 815 foreach ($group->spaces as $space) {
618 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 + }
619 822 }
620 823 }
621 824
622 - return [
623 - '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
624 843 ];
844 + return apply_filters('fluent_community/space_groups_api_response', $data, $request->all());
625 845 }
626 846
627 847 public function createSpaceGroup(Request $request)
628 848 {
629 - $user = $this->getUser(true);
630 - if (!$user->isCommunityModerator()) {
631 - return $this->sendError([
632 - 'message' => 'You are not allowed to create space group'
633 - ]);
634 - }
635 -
636 849 $data = $request->all();
637 850
638 851 $this->validate($data, [
639 852 'title' => 'required|unique:fcom_spaces,title',
@@ -639,13 +852,28 @@
639 852 'title' => 'required|unique:fcom_spaces,title',
640 853 'slug' => 'required|unique:fcom_spaces,slug'
641 854 ]);
642 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', ''));
643 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 +
644 872 $formattedData = [
645 - 'title' => sanitize_text_field($data['title']),
646 - 'slug' => sanitize_title($data['slug']),
647 - 'description' => sanitize_textarea_field($data['description']),
873 + 'title' => $title,
874 + 'slug' => $slug,
875 + 'description' => $desc,
648 876 'status' => 'active',
649 877 'type' => 'space_group',
650 878 'settings' => [
651 879 'always_show_spaces' => Arr::get($data, 'settings.always_show_spaces', 'yes'),
@@ -662,15 +890,8 @@
662 890 }
663 891
664 892 public function updateSpaceGroup(Request $request, $groupId)
665 893 {
666 - $user = $this->getUser();
667 - if (!$user || !$user->isCommunityModerator()) {
668 - return $this->sendError([
669 - 'message' => 'You are not allowed to create space group'
670 - ]);
671 - }
672 -
673 894 $group = SpaceGroup::findOrFail($groupId);
674 895 $data = $request->all();
675 896
676 897 $this->validate($data, [
@@ -682,9 +903,9 @@
682 903 ->first();
683 904
684 905 if ($taken) {
685 906 return $this->sendError([
686 - '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')
687 908 ]);
688 909 }
689 910
690 911 $formattedData = [
@@ -699,9 +920,9 @@
699 920
700 921 $group->fill($formattedData)->save();
701 922
702 923 return [
703 - 'message' => __('Space group has been created updated', 'fluent-community'),
924 + 'message' => __('Space group has been updated', 'fluent-community'),
704 925 'group' => $group
705 926 ];
706 927 }
707 928
@@ -706,21 +927,13 @@
706 927 }
707 928
708 929 public function deleteSpaceGroup(Request $request, $groupId)
709 930 {
710 -
711 - $user = $this->getUser();
712 - if (!$user || !$user->isCommunityModerator()) {
713 - return $this->sendError([
714 - 'message' => 'You are not allowed to create space group'
715 - ]);
716 - }
717 -
718 931 $group = SpaceGroup::findOrFail($groupId);
719 932
720 933 if (!$group->spaces->isEmpty()) {
721 934 return $this->sendError([
722 - 'message' => 'You can not delete this group. It has spaces'
935 + 'message' => __('You cannot delete this group. It has spaces', 'fluent-community')
723 936 ]);
724 937 }
725 938
726 939 $group->delete();
@@ -741,9 +954,9 @@
741 954 ]);
742 955 }
743 956
744 957 return [
745 - 'message' => __('Space group indexes has been updated', 'fluent-community')
958 + 'message' => __('Space group indexes have been updated.', 'fluent-community')
746 959 ];
747 960
748 961 }
749 962
@@ -758,9 +971,9 @@
758 971 ]);
759 972 }
760 973
761 974 return [
762 - 'message' => __('Space indexes has been updated', 'fluent-community')
975 + 'message' => __('Space indexes have been updated.', 'fluent-community')
763 976 ];
764 977 }
765 978
766 979 public function moveSpace(Request $request)
@@ -771,9 +984,9 @@
771 984 $space = BaseSpace::withoutGlobalScopes()->findOrFail($spaceId);
772 985 $group = SpaceGroup::findOrFail($groupId);
773 986
774 987 $space->update([
775 - 'parent_id' => $groupId
988 + 'parent_id' => $group->id
776 989 ]);
777 990
778 991 return [
779 992 'message' => __('Space has been moved successfully', 'fluent-community')
@@ -781,28 +994,58 @@
781 994 }
782 995
783 996 public function getLockScreenSettings(Request $request, $spaceSlug)
784 997 {
785 - $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 +
786 1009 $lockscreen = $space->getLockscreen();
787 1010
1011 + $lockscreen = apply_filters('fluent_community/get_lockscreen_settings', $lockscreen, $space);
1012 +
788 1013 return [
789 1014 'lockscreen' => $lockscreen
790 1015 ];
791 1016 }
792 1017
793 - public function updateLockscreenSettings(Request $request, $spaceSlug)
1018 + public function getMetaSettings(Request $request, $spaceSlug)
794 1019 {
795 1020 $space = Space::where('slug', $spaceSlug)->firstOrFail();
1021 + $metaSettings = apply_filters('fluent_community/space/meta_fields', [], $space);
796 1022
797 - $settingFields = $request->get('lockscreen', []);
1023 + if (!$metaSettings) {
1024 + return [
1025 + 'meta_settings' => null
1026 + ];
1027 + }
798 1028
799 - $formattedFields = LockscreenService::formatLockscreenFields($settingFields);
1029 + return [
1030 + 'meta_settings' => $metaSettings
1031 + ];
1032 + }
800 1033
801 - $space->setLockscreen($formattedFields);
1034 + private function getActiveMemberCounts($spaceIds)
1035 + {
1036 + if (!$spaceIds) {
1037 + return [];
1038 + }
802 1039
803 - return [
804 - 'message' => 'Lockscreen settings has been updated successfully.',
805 - 'community' => $space
806 - ];
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();
807 1050 }
808 1051 }