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/Models/BaseSpace.php +257 -45 1.0.952.10.0 View file →
@@ -1,8 +1,9 @@
1 1 <?php
2 2
3 3 namespace FluentCommunity\App\Models;
4 4
5 +use FluentCommunity\App\App;
5 6 use FluentCommunity\App\Functions\Utility;
6 7 use FluentCommunity\App\Services\CustomSanitizer;
7 8 use FluentCommunity\App\Services\LockscreenService;
8 9 use FluentCommunity\App\Services\Helper;
@@ -7,8 +8,31 @@
7 8 use FluentCommunity\App\Services\LockscreenService;
8 9 use FluentCommunity\App\Services\Helper;
9 10 use FluentCommunity\Framework\Support\Arr;
10 11
12 +/**
13 + * @property int $id
14 + * @property int|null $created_by
15 + * @property int|null $parent_id
16 + * @property string $title
17 + * @property string $slug
18 + * @property string|null $description
19 + * @property string|null $logo
20 + * @property string|null $cover_photo
21 + * @property string|null $type
22 + * @property string|null $privacy
23 + * @property string|null $status
24 + * @property int|null $serial
25 + * @property array $settings
26 + * @property string|null $created_at
27 + * @property string|null $updated_at
28 + * @property array|null $permissions
29 + * @property string|null $description_rendered
30 + * @property mixed $membership
31 + * @property array|null $topics
32 + * @property array|null $header_links
33 + * @property array|null $lockscreen_config
34 + */
11 35 class BaseSpace extends Model
12 36 {
13 37 protected $table = 'fcom_spaces';
14 38
@@ -13,9 +37,9 @@
13 37 protected $table = 'fcom_spaces';
14 38
15 39 protected static $type = 'community';
16 40
17 - protected $guarded = ['id'];
41 + protected $guarded = [ 'id' ];
18 42
19 43 protected $fillable = [
20 44 'created_by',
21 45 'parent_id',
@@ -27,19 +51,23 @@
27 51 'type',
28 52 'privacy',
29 53 'status',
30 54 'serial',
31 - 'settings'
55 + 'settings',
32 56 ];
33 57
34 58 protected $searchable = [
35 59 'title',
36 - 'description'
60 + 'description',
37 61 ];
38 62
63 + public $_preloadedMembershipUserId = null;
64 + public $_preloadedMembership = null;
65 +
39 66 public static function boot()
40 67 {
41 68 parent::boot();
69 +
42 70 static::creating(function ($model) {
43 71 if (empty($model->created_by)) {
44 72 $model->created_by = get_current_user_id();
45 73 }
@@ -44,11 +72,11 @@
44 72 $model->created_by = get_current_user_id();
45 73 }
46 74
47 75 if (empty($model->slug)) {
48 - $slug = sanitize_title($model->tilte, time());
49 - $model->slug = $slug;
76 + $model->slug = self::generateNewSlug($model);
50 77 }
78 +
51 79 $model->type = static::$type;
52 80 });
53 81
54 82 static::addGlobalScope('type', function ($query) {
@@ -60,11 +88,11 @@
60 88 });
61 89
62 90 static::deleting(function ($space) {
63 91 Media::where('sub_object_id', $space->id)
64 - ->whereIn('object_source', ['space_logo', 'space_cover_photo'])
92 + ->whereIn('object_source', [ 'space_logo', 'space_cover_photo' ])
65 93 ->update([
66 - 'is_active' => 0
94 + 'is_active' => 0,
67 95 ]);
68 96 });
69 97 }
70 98
@@ -77,8 +105,17 @@
77 105 {
78 106 return $this->belongsTo(SpaceUserPivot::class, 'id', 'space_id');
79 107 }
80 108
109 + public function admins()
110 + {
111 + return $this->belongsToMany(User::class, 'fcom_space_user', 'space_id', 'user_id')
112 + ->where(function ($query) {
113 + $query->where('role', 'admin')
114 + ->orWhere('role', 'moderator');
115 + });
116 + }
117 +
81 118 public function scopeSearchBy($query, $search)
82 119 {
83 120 if ($search) {
84 121 $fields = $this->searchable;
@@ -92,8 +129,13 @@
92 129
93 130 return $query;
94 131 }
95 132
133 + public function scopeOnlyMain($query)
134 + {
135 + return $query->withoutGlobalScopes()->whereIn('type', [ 'community', 'course' ]);
136 + }
137 +
96 138 public function scopeFilterByUserId($query, $userId)
97 139 {
98 140 if (!$userId) {
99 141 return $query->where('privacy', 'public');
@@ -126,18 +168,23 @@
126 168 {
127 169 return $this->hasMany(Feed::class, 'space_id', 'id');
128 170 }
129 171
172 + public function comments()
173 + {
174 + return $this->hasManyThrough(Comment::class, Feed::class, 'space_id', 'post_id');
175 + }
176 +
130 177 public function members()
131 178 {
132 179 return $this->belongsToMany(User::class, 'fcom_space_user', 'space_id', 'user_id')
133 - ->withPivot(['role', 'created_at', 'status']);
180 + ->withPivot([ 'role', 'created_at', 'status' ]);
134 181 }
135 182
136 183 public function x_members()
137 184 {
138 185 return $this->belongsToMany(XProfile::class, 'fcom_space_user', 'space_id', 'user_id', 'id', 'user_id')
139 - ->withPivot(['role', 'created_at', 'status']);
186 + ->withPivot([ 'role', 'created_at', 'status' ]);
140 187 }
141 188
142 189 public function group()
143 190 {
@@ -149,11 +196,43 @@
149 196 if (!$userId) {
150 197 return null;
151 198 }
152 199
200 + if ($this->_preloadedMembershipUserId == $userId) {
201 + return $this->_preloadedMembership;
202 + }
203 +
153 204 return $this->members()->where('user_id', $userId)->first();
154 205 }
155 206
207 + public static function preloadMemberships($spaces, $userId)
208 + {
209 + if (!$userId || $spaces->isEmpty()) {
210 + return;
211 + }
212 +
213 + $spaceIds = $spaces->pluck('id')->toArray();
214 +
215 + $pivots = SpaceUserPivot::where('user_id', $userId)
216 + ->whereIn('space_id', $spaceIds)
217 + ->get()
218 + ->keyBy('space_id');
219 +
220 + foreach ($spaces as $space) {
221 + $space->_preloadedMembershipUserId = $userId;
222 + $pivot = $pivots->get($space->id);
223 + $space->_preloadedMembership = $pivot ? self::pivotToMembership($pivot) : null;
224 + }
225 + }
226 +
227 + private static function pivotToMembership($pivot)
228 + {
229 + $membership = new \stdClass();
230 + $membership->pivot = (object) $pivot->toArray();
231 + $membership->ID = $pivot->user_id;
232 + return $membership;
233 + }
234 +
156 235 public function isCourseSpace()
157 236 {
158 237 return $this->type == 'course';
159 238 }
@@ -163,19 +242,22 @@
163 242 if (Helper::isSiteAdmin($userId)) {
164 243 return true;
165 244 }
166 245
246 + $roles = [ 'admin' ];
247 +
248 + if ($checkModerator) {
249 + if (Helper::isModerator()) {
250 + return true;
251 + }
252 + $roles[] = 'moderator';
253 + }
254 +
167 255 $membership = $this->getMembership($userId);
168 256 if (!$membership) {
169 257 return false;
170 258 }
171 259
172 - $roles = ['admin'];
173 -
174 - if ($checkModerator) {
175 - $roles[] = 'moderator';
176 - }
177 -
178 260 return in_array($membership->pivot->role, $roles);
179 261 }
180 262
181 263 public function updateCustomData($data, $removeSrc = false)
@@ -203,31 +285,28 @@
203 285 $this->privacy = sanitize_text_field($data['privacy']);
204 286 }
205 287
206 288 if (isset($data['parent_id'])) {
207 - $group = SpaceGroup::find($data['parent_id']);
208 - if (!$group) {
209 - throw new \Exception('Invalid group id', 400);
289 + if (!empty($data['parent_id'])) {
290 + $group = SpaceGroup::find($data['parent_id']);
291 + if (!$group) {
292 + throw new \Exception(esc_html__('Invalid group id', 'fluent-community'), 400);
293 + }
294 + $this->parent_id = $group->id;
295 + } else {
296 + $this->parent_id = null;
210 297 }
211 -
212 - $this->parent_id = $group->id;
213 298 }
214 299
215 300 if (isset($data['settings'])) {
216 - $shapSvg = '';
217 - if (!empty($data['settings']['shape_svg'])) {
218 - $shapSvg = CustomSanitizer::sanitizeSvg($data['settings']['shape_svg']);
219 - }
220 301
221 - $exisitingSetting = $this->settings;
222 - $settings = Arr::only(fluentCommunitySanitizeArray($data['settings']), array_keys($this->defaultSettings()));
302 + $settings = CustomSanitizer::santizeSpaceSettings($data['settings'], $this->privacy);
223 303
224 - if ($shapSvg) {
225 - $settings['shape_svg'] = $shapSvg;
226 - } else if (!empty($settings['emoji'])) {
227 - $settings['emoji'] = CustomSanitizer::sanitizeEmoji($settings['emoji']);
304 + if (is_wp_error($settings)) {
305 + return $settings;
228 306 }
229 307
308 + $exisitingSetting = $this->settings;
230 309 $settings['links'] = Arr::get($exisitingSetting, 'links', []);
231 310
232 311 if (isset($settings['og_image'])) {
233 312 $ogImageUrl = Arr::get($settings, 'og_image');
@@ -234,14 +313,14 @@
234 313 if ($ogImageUrl) {
235 314 $ogImageMedia = Helper::getMediaFromUrl($ogImageUrl);
236 315 if ($ogImageMedia && $ogImageMedia->is_active) {
237 316 unset($settings['og_image']);
238 - } else if ($ogImageMedia) {
317 + } elseif ($ogImageMedia) {
239 318 $ogImageMedia->update([
240 319 'is_active' => true,
241 320 'user_id' => get_current_user_id(),
242 321 'sub_object_id' => $this->id,
243 - 'object_source' => 'space_og_media'
322 + 'object_source' => 'space_og_media',
244 323 ]);
245 324 $settings['og_image'] = $ogImageMedia->public_url;
246 325 } else {
247 326 $settings['og_image'] = sanitize_url($ogImageUrl);
@@ -254,8 +333,28 @@
254 333 $settings = wp_parse_args($settings, $exisitingSetting);
255 334 $this->settings = $settings;
256 335 }
257 336
337 + if (!empty($data['slug']) && $data['slug'] !== $this->slug) {
338 + $newSlug = preg_replace('/[^a-zA-Z0-9-_]/', '', $data['slug']);
339 +
340 + $newSlug = sanitize_title($newSlug);
341 +
342 + if (empty($newSlug)) {
343 + throw new \Exception('Invalid slug', 400);
344 + }
345 +
346 + $exist = App::getInstance('db')->table('fcom_spaces')->where('slug', $newSlug)
347 + ->where('id', '!=', $this->id)
348 + ->exists();
349 +
350 + if ($exist) {
351 + throw new \Exception(esc_html__('Slug already exists. Please use a different slug', 'fluent-community'), 400);
352 + }
353 +
354 + $this->slug = $newSlug;
355 + }
356 +
258 357 $deletePhotos = array_filter($deletePhotos);
259 358 if ($removeSrc && $deletePhotos) {
260 359 $deletePhotos = array_filter($deletePhotos);
261 360 do_action('fluent_community/remove_medias_by_url', $deletePhotos, [
@@ -274,17 +373,29 @@
274 373
275 374 return $this;
276 375 }
277 376
377 + public function isContentSpace()
378 + {
379 + return in_array($this->type, [ 'community', 'course' ]);
380 + }
381 +
278 382 public function getSettingsAttribute($value)
279 383 {
280 - $settings = maybe_unserialize($value);
384 + $settings = Utility::safeUnserialize($value);
281 385
282 386 if (!$settings) {
283 387 $settings = [];
284 388 }
285 389
286 - return wp_parse_args($settings, $this->defaultSettings());
390 + $settings = wp_parse_args($settings, $this->defaultSettings());
391 +
392 + if (!defined('FLUENT_COMMUNITY_PRO')) {
393 + $settings['document_library'] = 'no';
394 + $settings['media_gallery'] = 'no';
395 + }
396 +
397 + return $settings;
287 398 }
288 399
289 400 public function defaultSettings()
290 401 {
@@ -297,8 +408,10 @@
297 408 'layout_style' => 'timeline',
298 409 'show_sidebar' => 'yes',
299 410 'og_image' => '',
300 411 'links' => [],
412 + 'topic_required' => 'no',
413 + 'hide_members_count' => 'no', // yes / no
301 414 'members_page_status' => 'members_only', // members_only, everybody, logged_in, admin_only
302 415 ];
303 416 }
304 417
@@ -309,13 +422,19 @@
309 422
310 423 public function getPublicPermissions()
311 424 {
312 425 if ($this->privacy == 'public') {
426 +
427 + $hasDocuments = defined('FLUENT_COMMUNITY_PRO') && Arr::get($this->settings, 'document_library') == 'yes';
428 + $hasMediaGallery = defined('FLUENT_COMMUNITY_PRO') && Arr::get($this->settings, 'media_gallery') == 'yes';
429 +
313 430 return [
314 - 'can_view_info' => true,
315 - 'can_view_posts' => true,
316 - 'can_view_members' => $this->canViewMembers(null),
317 - 'can_create_post' => false
431 + 'can_view_info' => true,
432 + 'can_view_posts' => true,
433 + 'can_view_members' => $this->canViewMembers(null),
434 + 'can_create_post' => false,
435 + 'can_view_documents' => $hasDocuments && Arr::get($this->settings, 'document_access') == 'everybody',
436 + 'can_view_media' => $hasMediaGallery && Arr::get($this->settings, 'media_access') == 'everybody',
318 437 ];
319 438 }
320 439
321 440 return [
@@ -348,9 +467,10 @@
348 467 return true;
349 468 }
350 469
351 470 if ($viewStatus === 'members_only') {
352 - return Helper::isUserInSpace($user->ID, $this->id);
471 + $membership = $this->getMembership($user->ID);
472 + return $membership && isset($membership->pivot) && $membership->pivot->status === 'active';
353 473 }
354 474
355 475 return $this->isAdmin($user->ID, true);
356 476 }
@@ -361,9 +481,10 @@
361 481
362 482 $hasPermission = $permissions[$permission] ?? false;
363 483
364 484 if (!$hasPermission && $exception) {
365 - throw new \Exception('Sorry you do not have permission ' . esc_html($permission), 403);
485 + /* translators: %s is the permission name */
486 + throw new \Exception(esc_html(sprintf(__('Sorry you do not have permission %s', 'fluent-community'), $permission)), 403);
366 487 }
367 488
368 489 return $hasPermission;
369 490 }
@@ -382,8 +503,15 @@
382 503 if ($this->type == 'course') {
383 504 return Helper::baseUrl('course/' . $this->slug . '/lessons');
384 505 }
385 506
507 + if ($this->type == 'sidebar_link') {
508 + $permalink = Arr::get($this->settings, 'permalink');
509 + if ($permalink) {
510 + return $permalink;
511 + }
512 + }
513 +
386 514 return Helper::baseUrl('/');
387 515 }
388 516
389 517 public function getLockscreen()
@@ -390,8 +518,13 @@
390 518 {
391 519 return LockscreenService::getLockscreenSettings($this);
392 520 }
393 521
522 + public function hasPaywallIntegration()
523 + {
524 + return !empty(Arr::get($this->settings, 'cart_product_ids', []));
525 + }
526 +
394 527 public function getCustomMeta($key, $default = null)
395 528 {
396 529 return Helper::getSpaceMeta($this->id, $key, $default);
397 530 }
@@ -407,16 +540,16 @@
407 540 return '';
408 541 }
409 542
410 543 if ($this->logo) {
411 - return '<img alt="" src="' . $this->logo . '"/>';
544 + return '<img alt="" src="' . esc_url($this->logo) . '"/>';
412 545 }
413 546
414 - if ($imoji = Arr::get($this->settings, 'emoji')) {
547 + if ($imoji = Arr::get($this->settings, 'emoji')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
415 548 return '<span class="fcom_emoji">' . $imoji . '</span>';
416 549 }
417 550
418 - if ($svg = Arr::get($this->settings, 'shape_svg')) {
551 + if ($svg = Arr::get($this->settings, 'shape_svg')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
419 552 return '<span class="fcom_shape"><i class="el-icon">' . $svg . '</i></span>';
420 553 }
421 554
422 555 return '';
@@ -441,10 +574,10 @@
441 574
442 575 // We have to create one
443 576 $new = Meta::create([
444 577 'object_id' => $topicId,
445 - 'meta_key' => $this->id,
446 - 'object_type' => 'term_space_relation'
578 + 'meta_key' => $this->id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
579 + 'object_type' => 'term_space_relation',
447 580 ]);
448 581
449 582 $existIds[] = $new->id;
450 583 }
@@ -459,5 +592,84 @@
459 592
460 593 return $this;
461 594 }
462 595
596 + protected static function generateNewSlug($newModel)
597 + {
598 + if ($newModel->title) {
599 + // Remove the emojis
600 + $title = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $newModel->title);
601 + // get the first 30 char from the title
602 + $title = mb_substr($title, 0, 30, 'UTF-8');
603 + } else {
604 + $title = static::$type . '-' . time();
605 + }
606 +
607 + $title = Helper::normalizeToAscii($title);
608 + $title = remove_accents($title);
609 +
610 + $title = strtolower($title);
611 +
612 + $title = trim(preg_replace('/[^a-z0-9-_]/', ' ', $title));
613 +
614 + $slugNum = self::withoutGlobalScopes()->where('type', static::$type)->count();
615 +
616 + $title = sanitize_title($title, static::$type . '-' . $slugNum);
617 +
618 + // check if the slug is already exists
619 + $slug = $title;
620 + $count = 1;
621 + while (self::withoutGlobalScopes()->where('slug', $slug)->exists()) {
622 + if ($count == 5) {
623 + $count = time();
624 + }
625 + $slug = $title . '-' . (++$slugNum);
626 + ++$count;
627 + }
628 +
629 + return $slug;
630 + }
631 +
632 + public function formatSpaceData($user)
633 + {
634 + $userId = $user ? $user->ID : null;
635 +
636 + $this->permissions = $this->getUserPermissions($user);
637 + $this->description_rendered = wpautop($this->description);
638 + $this->membership = $this->getMembership($userId);
639 + $this->topics = Utility::getTopicsBySpaceId($this->id);
640 +
641 + $headerLinks = [
642 + [
643 + 'title' => __('Posts', 'fluent-community'),
644 + 'route' => [
645 + 'name' => 'space_feeds',
646 + ],
647 + ],
648 + ];
649 +
650 + if (Arr::get($this->permissions, 'can_view_members')) {
651 + $headerLinks[] = [
652 + 'title' => __('Members', 'fluent-community'),
653 + 'route' => [
654 + 'name' => 'space_members',
655 + ],
656 + ];
657 + }
658 +
659 + $this->header_links = apply_filters('fluent_community/space_header_links', $headerLinks, $this);
660 +
661 + if ($this->isAdmin($userId, true)) {
662 + return $this;
663 + }
664 +
665 + $this->lockscreen_config = LockscreenService::getLockscreenConfig($this, $this->membership, true);
666 +
667 + $spaceSettings = $this->settings;
668 +
669 + $spaceSettings['links'] = Helper::filterAccessibleLinks(Arr::get($spaceSettings, 'links', []), $user);
670 +
671 + $this->settings = $spaceSettings;
672 +
673 + return $this;
674 + }
463 675 }