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.11.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 All 78 releases
← All changes | app/Models/BaseSpace.php +256 -45 1.1.02.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,9 @@
297 408 'layout_style' => 'timeline',
298 409 'show_sidebar' => 'yes',
299 410 'og_image' => '',
300 411 'links' => [],
412 + 'topic_required' => 'no',
301 413 'hide_members_count' => 'no', // yes / no
302 414 'members_page_status' => 'members_only', // members_only, everybody, logged_in, admin_only
303 415 ];
304 416 }
@@ -310,13 +422,19 @@
310 422
311 423 public function getPublicPermissions()
312 424 {
313 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 +
314 430 return [
315 - 'can_view_info' => true,
316 - 'can_view_posts' => true,
317 - 'can_view_members' => $this->canViewMembers(null),
318 - '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',
319 437 ];
320 438 }
321 439
322 440 return [
@@ -349,9 +467,10 @@
349 467 return true;
350 468 }
351 469
352 470 if ($viewStatus === 'members_only') {
353 - return Helper::isUserInSpace($user->ID, $this->id);
471 + $membership = $this->getMembership($user->ID);
472 + return $membership && isset($membership->pivot) && $membership->pivot->status === 'active';
354 473 }
355 474
356 475 return $this->isAdmin($user->ID, true);
357 476 }
@@ -362,9 +481,10 @@
362 481
363 482 $hasPermission = $permissions[$permission] ?? false;
364 483
365 484 if (!$hasPermission && $exception) {
366 - 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);
367 487 }
368 488
369 489 return $hasPermission;
370 490 }
@@ -383,8 +503,15 @@
383 503 if ($this->type == 'course') {
384 504 return Helper::baseUrl('course/' . $this->slug . '/lessons');
385 505 }
386 506
507 + if ($this->type == 'sidebar_link') {
508 + $permalink = Arr::get($this->settings, 'permalink');
509 + if ($permalink) {
510 + return $permalink;
511 + }
512 + }
513 +
387 514 return Helper::baseUrl('/');
388 515 }
389 516
390 517 public function getLockscreen()
@@ -391,8 +518,13 @@
391 518 {
392 519 return LockscreenService::getLockscreenSettings($this);
393 520 }
394 521
522 + public function hasPaywallIntegration()
523 + {
524 + return !empty(Arr::get($this->settings, 'cart_product_ids', []));
525 + }
526 +
395 527 public function getCustomMeta($key, $default = null)
396 528 {
397 529 return Helper::getSpaceMeta($this->id, $key, $default);
398 530 }
@@ -408,16 +540,16 @@
408 540 return '';
409 541 }
410 542
411 543 if ($this->logo) {
412 - return '<img alt="" src="' . $this->logo . '"/>';
544 + return '<img alt="" src="' . esc_url($this->logo) . '"/>';
413 545 }
414 546
415 - if ($imoji = Arr::get($this->settings, 'emoji')) {
547 + if ($imoji = Arr::get($this->settings, 'emoji')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
416 548 return '<span class="fcom_emoji">' . $imoji . '</span>';
417 549 }
418 550
419 - if ($svg = Arr::get($this->settings, 'shape_svg')) {
551 + if ($svg = Arr::get($this->settings, 'shape_svg')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
420 552 return '<span class="fcom_shape"><i class="el-icon">' . $svg . '</i></span>';
421 553 }
422 554
423 555 return '';
@@ -442,10 +574,10 @@
442 574
443 575 // We have to create one
444 576 $new = Meta::create([
445 577 'object_id' => $topicId,
446 - 'meta_key' => $this->id,
447 - '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',
448 580 ]);
449 581
450 582 $existIds[] = $new->id;
451 583 }
@@ -460,5 +592,84 @@
460 592
461 593 return $this;
462 594 }
463 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 + }
464 675 }