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