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 +427 -172 1.0.922.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,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,30 +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 + }
375 +
232 376 return [
233 - 'message' => __('Space has been updated', 'fluent-community')
377 + 'message' => __('Settings have been updated', 'fluent-community'),
378 + 'redirect_url' => $slugUpdated ? $space->getPermalink() : ''
234 379 ];
235 380 }
236 381
382 + public function patchById(Request $request, $id)
383 + {
384 + $space = Space::findOrFail($id);
385 +
386 + return $this->patchBySlug($request, $space->slug);
387 + }
388 +
237 389 public function getMembers(Request $request, $slug)
238 390 {
391 + /** @var Space $space */
239 392 $space = Space::where('slug', $slug)
240 393 ->firstOrFail();
241 394
242 395 $user = $this->getUser();
243 - $space->verifyUserPermisson($user, 'can_view_members');
244 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 +
245 404 $search = $request->getSafe('search', 'sanitize_text_field');
246 405
247 406 $pendingCount = 0;
248 407 if ($user && $user->can('can_add_member', $space)) {
@@ -261,30 +420,48 @@
261 420 }])
262 421 ->where('status', 'pending')
263 422 ->paginate();
264 423
265 - return [
424 + return apply_filters('fluent_community/space_members_api_response', [
266 425 'members' => $pendingRequests,
267 426 'pending_count' => $pendingCount
268 - ];
427 + ], $pendingRequests, $request->all());
269 428 }
270 429 }
271 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 +
272 445 $spaceMembers = SpaceUserPivot::bySpace($space->id)
273 446 ->whereHas('xprofile', function ($q) use ($search) {
274 - return $q->searchBy($search)
275 - ->where('status', 'active');
447 + $q->searchBy($search)->where('status', 'active');
276 448 })
449 + ->where('fcom_space_user.status', 'active')
277 450 ->with(['xprofile' => function ($q) {
278 451 $q->select(ProfileHelper::getXProfilePublicFields());
279 452 }])
280 - ->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)
281 458 ->paginate();
282 459
283 - return [
460 + return apply_filters('fluent_community/space_members_api_response', [
284 461 'members' => $spaceMembers,
285 462 'pending_count' => $pendingCount
286 - ];
463 + ], $spaceMembers, $request->all());
287 464 }
288 465
289 466 public function join(Request $request, $slug)
290 467 {
@@ -291,9 +468,9 @@
291 468 $space = Space::where('slug', $slug)->first();
292 469
293 470 if (!$space) {
294 471 return $this->sendError([
295 - 'message' => 'Space not found'
472 + 'message' => __('Space not found', 'fluent-community')
296 473 ]);
297 474 }
298 475
299 476 $user = $this->getUser();
@@ -301,24 +478,29 @@
301 478 $membership = $space->getMembership(get_current_user_id());
302 479
303 480 if ($membership) {
304 481 return $this->sendError([
305 - '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')
306 483 ]);
307 484 }
308 485
309 486 $roles = $user->getCommunityRoles();
487 + $isPrivileged = !!array_intersect($roles, ['admin', 'moderator']);
310 488
311 - if (!$roles && $space->privacy == 'secret') {
489 + if (!$isPrivileged && $space->privacy == 'secret') {
312 490 return $this->sendError([
313 - 'message' => 'You are not allowed to join this space'
491 + 'message' => __('You are not allowed to join this space', 'fluent-community')
314 492 ]);
315 493 }
316 494
317 495 $status = 'active';
318 - if (!$roles) {
496 + if (!$isPrivileged) {
319 497 if ($space->privacy != 'public') {
320 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 + }
321 503 }
322 504 $role = 'member';
323 505 } else {
324 506 $role = $user->isCommunityAdmin() ? 'admin' : 'moderator';
@@ -323,8 +505,9 @@
323 505 } else {
324 506 $role = $user->isCommunityAdmin() ? 'admin' : 'moderator';
325 507 }
326 508
509 +
327 510 $space->members()->attach(get_current_user_id(), [
328 511 'role' => $role,
329 512 'status' => $status
330 513 ]);
@@ -339,9 +522,9 @@
339 522
340 523 $user->cacheAccessSpaces();
341 524
342 525 return [
343 - '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'),
344 527 'membership' => $space->membership
345 528 ];
346 529 }
347 530
@@ -351,9 +534,9 @@
351 534 $space = Space::where('slug', $slug)->first();
352 535
353 536 if (!$space) {
354 537 return $this->sendError([
355 - 'message' => 'Space not found'
538 + 'message' => __('Space not found', 'fluent-community')
356 539 ]);
357 540 }
358 541
359 542 $membership = $space->getMembership($user->ID);
@@ -359,9 +542,9 @@
359 542 $membership = $space->getMembership($user->ID);
360 543
361 544 if (!$membership) {
362 545 return $this->sendError([
363 - 'message' => 'You are not a member of this community'
546 + 'message' => __('You are not a member of this community', 'fluent-community')
364 547 ]);
365 548 }
366 549
367 550 Helper::removeFromSpace($space, $user->ID, 'self');
@@ -370,24 +553,18 @@
370 553 'message' => __('You have left this space', 'fluent-community')
371 554 ];
372 555 }
373 556
374 - public function delete(Request $request, $slug)
557 + public function deleteBySlug(Request $request, $slug)
375 558 {
376 559 $space = Space::where('slug', $slug)->first();
377 560
378 561 if (!$space) {
379 562 return $this->sendError([
380 - 'message' => 'Space not found'
563 + 'message' => __('Space not found', 'fluent-community')
381 564 ]);
382 565 }
383 566
384 - if (!Helper::isSiteAdmin()) {
385 - return $this->sendError([
386 - 'message' => 'You are not allowed to delete this community'
387 - ]);
388 - }
389 -
390 567 do_action('fluent_community/space/before_delete', $space);
391 568
392 569 Comment::whereHas('post', function ($q) use ($space) {
393 570 $q->where('space_id', $space->id);
@@ -410,8 +587,15 @@
410 587 'message' => __('Space has been deleted successfully', 'fluent-community')
411 588 ];
412 589 }
413 590
591 + public function deleteById(Request $request, $id)
592 + {
593 + $space = Space::findOrFail($id);
594 +
595 + return $this->deleteBySlug($request, $space->slug);
596 + }
597 +
414 598 public function addMember(Request $request, $slug)
415 599 {
416 600 $space = Space::where('slug', $slug)->first();
417 601
@@ -416,9 +600,9 @@
416 600 $space = Space::where('slug', $slug)->first();
417 601
418 602 if (!$space) {
419 603 return $this->sendError([
420 - 'message' => 'Space not found'
604 + 'message' => __('Space not found', 'fluent-community')
421 605 ]);
422 606 }
423 607
424 608 $this->validate($request->all(), [
@@ -426,9 +610,10 @@
426 610 ]);
427 611
428 612 $userId = $request->get('user_id');
429 613 $targetUser = User::findOrFail($userId);
430 - $xprofile = $targetUser->syncXProfile();
614 + $targetUser->syncXProfile();
615 + $xprofile = $targetUser->xprofile;
431 616
432 617 if ($xprofile && $xprofile->status != 'active') {
433 618 return $this->sendError([
434 619 'message' => __('Selected user is not active', 'fluent-community')
@@ -434,16 +619,15 @@
434 619 'message' => __('Selected user is not active', 'fluent-community')
435 620 ]);
436 621 }
437 622
438 - $admin = User::find(get_current_user_id());
439 - $admin->verifySpacePermission('can_add_member', $space);
440 -
441 623 $pivot = SpaceUserPivot::bySpace($space->id)
442 624 ->byUser($userId)
443 625 ->first();
444 626
445 - $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';
446 630
447 631 if ($pivot) {
448 632 if ($pivot->status == 'active') {
449 633 if ($role != $pivot->role) {
@@ -452,20 +636,20 @@
452 636
453 637 do_action('fluent_community/space/member/role_updated', $space, $pivot);
454 638
455 639 return [
456 - 'message' => 'Member role updated'
640 + 'message' => __('Member role updated', 'fluent-community')
457 641 ];
458 642 }
459 643
460 644 return $this->sendError([
461 - 'message' => 'Selected user is already a member of this community'
645 + 'message' => __('Selected user is already a member of this community', 'fluent-community')
462 646 ]);
463 647 }
464 648
465 649 $pivot->status = 'active';
466 650 $pivot->save();
467 - do_action('fluent_community/space/joined', $space, $userId, 'approved');
651 + do_action('fluent_community/space/joined', $space, $userId, 'by_admin');
468 652
469 653 if ($role != 'member') {
470 654 do_action('fluent_community/space/member/role_updated', $space, $pivot);
471 655 }
@@ -470,9 +654,9 @@
470 654 do_action('fluent_community/space/member/role_updated', $space, $pivot);
471 655 }
472 656
473 657 return [
474 - 'message' => 'Member approved'
658 + 'message' => __('Member approved', 'fluent-community')
475 659 ];
476 660 }
477 661
478 662 $space->members()->attach($userId, [
@@ -484,9 +668,9 @@
484 668
485 669 do_action('fluent_community/space/joined', $space, $userId, 'by_admin');
486 670
487 671 return [
488 - 'message' => 'User has been added to this community'
672 + 'message' => __('User has been added to this Space', 'fluent-community')
489 673 ];
490 674 }
491 675
492 676 public function removeMember(Request $request, $slug)
@@ -494,17 +678,14 @@
494 678 $space = Space::where('slug', $slug)->first();
495 679
496 680 if (!$space) {
497 681 return $this->sendError([
498 - 'message' => 'Space not found'
682 + 'message' => __('Space not found', 'fluent-community')
499 683 ]);
500 684 }
501 685
502 686 $userId = $request->get('user_id');
503 687
504 - $admin = User::find(get_current_user_id());
505 - $admin->verifySpacePermission('can_remove_member', $space);
506 -
507 688 $pivot = SpaceUserPivot::bySpace($space->id)
508 689 ->byUser($userId)
509 690 ->first();
510 691
@@ -509,9 +690,9 @@
509 690 ->first();
510 691
511 692 if (!$pivot) {
512 693 return $this->sendError([
513 - 'message' => 'Selected user is not a member of this community'
694 + 'message' => __('Selected user is not a member of this community', 'fluent-community')
514 695 ]);
515 696 }
516 697
517 698 $pivot->delete();
@@ -524,9 +705,9 @@
524 705
525 706 do_action('fluent_community/space/user_left', $space, $userId, 'by_admin');
526 707
527 708 return [
528 - 'message' => 'User has been removed from this community'
709 + 'message' => __('User has been removed from this community', 'fluent-community')
529 710 ];
530 711 }
531 712
532 713 public function getOtherUsers(Request $request)
@@ -539,25 +720,53 @@
539 720
540 721 $isMod = $currentUser->isCommunityModerator() && current_user_can('list_users');
541 722
542 723 $spaceId = $request->get('space_id');
724 + $space = Space::findOrFail($spaceId);
543 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 +
544 732 $selects = ['ID', 'display_name'];
545 -
546 733 if ($isMod) {
547 734 $selects[] = 'user_email';
548 735 }
549 736
550 - $users = User::whereDoesntHave('spaces', function ($q) use ($spaceId) {
551 - return $q->where('space_id', $spaceId);
552 - })
553 - ->select($selects)
554 - ->searchBy($request->get('search'))
555 - ->paginate();
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'));
556 743
557 - 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 = [
558 766 'users' => $users
559 767 ];
768 + return apply_filters('fluent_community/space_non_members_api_response', $data, $request->all());
560 769 }
561 770
562 771 public function updateLinks(Request $request, $slug)
563 772 {
@@ -564,14 +773,12 @@
564 773 $space = Space::where('slug', $slug)->first();
565 774
566 775 if (!$space) {
567 776 return $this->sendError([
568 - 'message' => 'Space not found'
777 + 'message' => __('Space not found', 'fluent-community')
569 778 ]);
570 779 }
571 780
572 - $space->verifyUserPermisson($this->getUser(), 'community_admin');
573 -
574 781 $links = $request->get('links', []);
575 782
576 783 $links = array_map(function ($link) {
577 784 return CustomSanitizer::santizeLinkItem($link);
@@ -582,9 +789,9 @@
582 789 $space->settings = $settings;
583 790 $space->save();
584 791
585 792 return [
586 - 'message' => __('Links has been updated for the space', 'fluent-community'),
793 + 'message' => __('Links have been updated for the space', 'fluent-community'),
587 794 'links' => $links
588 795 ];
589 796 }
590 797
@@ -589,13 +796,16 @@
589 796 }
590 797
591 798 public function getSpaceGroups(Request $request)
592 799 {
593 - $user = $this->getUser(true);
594 - if (!$user->isCommunityModerator()) {
595 - return $this->sendError([
596 - 'message' => 'You are not allowed to create space group'
597 - ]);
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 + ];
598 808 }
599 809
600 810 $user = $this->getUser();
601 811
@@ -603,25 +813,40 @@
603 813
604 814 foreach ($groups as $group) {
605 815 foreach ($group->spaces as $space) {
606 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 + }
607 822 }
608 823 }
609 824
610 - return [
611 - '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
612 843 ];
844 + return apply_filters('fluent_community/space_groups_api_response', $data, $request->all());
613 845 }
614 846
615 847 public function createSpaceGroup(Request $request)
616 848 {
617 - $user = $this->getUser(true);
618 - if (!$user->isCommunityModerator()) {
619 - return $this->sendError([
620 - 'message' => 'You are not allowed to create space group'
621 - ]);
622 - }
623 -
624 849 $data = $request->all();
625 850
626 851 $this->validate($data, [
627 852 'title' => 'required|unique:fcom_spaces,title',
@@ -627,13 +852,28 @@
627 852 'title' => 'required|unique:fcom_spaces,title',
628 853 'slug' => 'required|unique:fcom_spaces,slug'
629 854 ]);
630 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', ''));
631 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 +
632 872 $formattedData = [
633 - 'title' => sanitize_text_field($data['title']),
634 - 'slug' => sanitize_title($data['slug']),
635 - 'description' => sanitize_textarea_field($data['description']),
873 + 'title' => $title,
874 + 'slug' => $slug,
875 + 'description' => $desc,
636 876 'status' => 'active',
637 877 'type' => 'space_group',
638 878 'settings' => [
639 879 'always_show_spaces' => Arr::get($data, 'settings.always_show_spaces', 'yes'),
@@ -650,15 +890,8 @@
650 890 }
651 891
652 892 public function updateSpaceGroup(Request $request, $groupId)
653 893 {
654 - $user = $this->getUser();
655 - if (!$user || !$user->isCommunityModerator()) {
656 - return $this->sendError([
657 - 'message' => 'You are not allowed to create space group'
658 - ]);
659 - }
660 -
661 894 $group = SpaceGroup::findOrFail($groupId);
662 895 $data = $request->all();
663 896
664 897 $this->validate($data, [
@@ -670,9 +903,9 @@
670 903 ->first();
671 904
672 905 if ($taken) {
673 906 return $this->sendError([
674 - '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')
675 908 ]);
676 909 }
677 910
678 911 $formattedData = [
@@ -687,9 +920,9 @@
687 920
688 921 $group->fill($formattedData)->save();
689 922
690 923 return [
691 - 'message' => __('Space group has been created updated', 'fluent-community'),
924 + 'message' => __('Space group has been updated', 'fluent-community'),
692 925 'group' => $group
693 926 ];
694 927 }
695 928
@@ -694,21 +927,13 @@
694 927 }
695 928
696 929 public function deleteSpaceGroup(Request $request, $groupId)
697 930 {
698 -
699 - $user = $this->getUser();
700 - if (!$user || !$user->isCommunityModerator()) {
701 - return $this->sendError([
702 - 'message' => 'You are not allowed to create space group'
703 - ]);
704 - }
705 -
706 931 $group = SpaceGroup::findOrFail($groupId);
707 932
708 933 if (!$group->spaces->isEmpty()) {
709 934 return $this->sendError([
710 - 'message' => 'You can not delete this group. It has spaces'
935 + 'message' => __('You cannot delete this group. It has spaces', 'fluent-community')
711 936 ]);
712 937 }
713 938
714 939 $group->delete();
@@ -729,9 +954,9 @@
729 954 ]);
730 955 }
731 956
732 957 return [
733 - 'message' => __('Space group indexes has been updated', 'fluent-community')
958 + 'message' => __('Space group indexes have been updated.', 'fluent-community')
734 959 ];
735 960
736 961 }
737 962
@@ -746,9 +971,9 @@
746 971 ]);
747 972 }
748 973
749 974 return [
750 - 'message' => __('Space indexes has been updated', 'fluent-community')
975 + 'message' => __('Space indexes have been updated.', 'fluent-community')
751 976 ];
752 977 }
753 978
754 979 public function moveSpace(Request $request)
@@ -759,9 +984,9 @@
759 984 $space = BaseSpace::withoutGlobalScopes()->findOrFail($spaceId);
760 985 $group = SpaceGroup::findOrFail($groupId);
761 986
762 987 $space->update([
763 - 'parent_id' => $groupId
988 + 'parent_id' => $group->id
764 989 ]);
765 990
766 991 return [
767 992 'message' => __('Space has been moved successfully', 'fluent-community')
@@ -769,28 +994,58 @@
769 994 }
770 995
771 996 public function getLockScreenSettings(Request $request, $spaceSlug)
772 997 {
773 - $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 +
774 1009 $lockscreen = $space->getLockscreen();
775 1010
1011 + $lockscreen = apply_filters('fluent_community/get_lockscreen_settings', $lockscreen, $space);
1012 +
776 1013 return [
777 1014 'lockscreen' => $lockscreen
778 1015 ];
779 1016 }
780 1017
781 - public function updateLockscreenSettings(Request $request, $spaceSlug)
1018 + public function getMetaSettings(Request $request, $spaceSlug)
782 1019 {
783 1020 $space = Space::where('slug', $spaceSlug)->firstOrFail();
1021 + $metaSettings = apply_filters('fluent_community/space/meta_fields', [], $space);
784 1022
785 - $settingFields = $request->get('lockscreen', []);
1023 + if (!$metaSettings) {
1024 + return [
1025 + 'meta_settings' => null
1026 + ];
1027 + }
786 1028
787 - $formattedFields = LockscreenService::formatLockscreenFields($settingFields);
1029 + return [
1030 + 'meta_settings' => $metaSettings
1031 + ];
1032 + }
788 1033
789 - $space->setLockscreen($formattedFields);
1034 + private function getActiveMemberCounts($spaceIds)
1035 + {
1036 + if (!$spaceIds) {
1037 + return [];
1038 + }
790 1039
791 - return [
792 - 'message' => 'Lockscreen settings has been updated successfully.',
793 - 'community' => $space
794 - ];
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();
795 1050 }
796 1051 }