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/Feed.php +105 -29 2.7.02.10.0 View file →
@@ -4,15 +4,45 @@
4 4
5 5 use FluentCommunity\App\Functions\Utility;
6 6 use FluentCommunity\App\Services\FeedsHelper;
7 7 use FluentCommunity\App\Services\Helper;
8 +use FluentCommunity\App\Services\ProfileHelper;
8 9 use FluentCommunityPro\App\Models\Follow;
9 10
11 +/**
12 + * @property int $id
13 + * @property int|null $user_id
14 + * @property string|null $title
15 + * @property string|null $slug
16 + * @property string|null $message
17 + * @property string|null $message_rendered
18 + * @property string|null $type
19 + * @property string|null $content_type
20 + * @property int|null $space_id
21 + * @property string|null $privacy
22 + * @property string|null $status
23 + * @property int|null $priority
24 + * @property string|null $featured_image
25 + * @property int|null $is_sticky
26 + * @property string|null $expired_at
27 + * @property string|null $scheduled_at
28 + * @property int|null $comments_count
29 + * @property int|null $reactions_count
30 + * @property array $meta
31 + * @property string|null $created_at
32 + * @property string|null $updated_at
33 + * @property-read User|null $user
34 + * @property-read BaseSpace|null $space
35 + * @property-read \FluentCommunity\Framework\Database\Orm\Collection $comments
36 + * @property bool|null $has_user_react
37 + * @property bool|null $bookmarked
38 + * @property string|null $default_comment_sort_by
39 + */
10 40 class Feed extends Model
11 41 {
12 42 protected $table = 'fcom_posts';
13 43
14 - protected $guarded = ['id'];
44 + protected $guarded = [ 'id' ];
15 45
16 46 protected $casts = [
17 47 'comments_count' => 'int',
18 48 'reactions_count' => 'int',
@@ -39,14 +69,14 @@
39 69 'comments_count',
40 70 'reactions_count',
41 71 'meta',
42 72 'created_at',
43 - 'updated_at'
73 + 'updated_at',
44 74 ];
45 75
46 76 protected $searchable = [
47 77 'message',
48 - 'title'
78 + 'title',
49 79 ];
50 80
51 81 public static $publicColumns = [
52 82 'id',
@@ -66,17 +96,28 @@
66 96 'status',
67 97 'is_sticky',
68 98 'scheduled_at',
69 99 'comments_count',
70 - 'reactions_count'
100 + 'reactions_count',
71 101 ];
72 102
73 103 protected $appends = [
74 - 'permalink'
104 + 'permalink',
75 105 ];
76 106
77 107 public static $scopeType = 'text';
78 108
109 + /**
110 + * The types a /post/{slug} URL can serve.
111 + *
112 + * fcom_posts is shared: it also holds course_lesson, course_section and
113 + * space_page rows, and each of those owns a different route. Any lookup that
114 + * drops the type global scope to reach image and article posts must narrow to
115 + * this list, or a feed URL can answer with a lesson or a page that happens to
116 + * share the slug. Add a new feed shape here when one is introduced.
117 + */
118 + public static $feedViewTypes = ['text', 'image', 'article'];
119 +
79 120 public static function boot()
80 121 {
81 122 parent::boot();
82 123
@@ -104,9 +145,9 @@
104 145
105 146 static::deleting(function ($feed) {
106 147 Media::where('feed_id', $feed->id)
107 148 ->update([
108 - 'is_active' => 0
149 + 'is_active' => 0,
109 150 ]);
110 151 Reaction::where('object_id', $feed->id)
111 152 ->where(function ($query) {
112 153 $query->where('object_type', 'feed')
@@ -125,9 +166,9 @@
125 166 // get the first 40 char from the title
126 167 $title = mb_substr($title, 0, 40, 'UTF-8');
127 168 } else {
128 169 // get the first 25 char from the message
129 - $title = mb_substr($newModel->message, 0, 40, 'UTF-8');
170 + $title = mb_substr((string) $newModel->message, 0, 40, 'UTF-8');
130 171 }
131 172
132 173 $title = Helper::normalizeToAscii($title);
133 174 $title = remove_accents($title);
@@ -146,9 +187,9 @@
146 187 if ($count == 5) {
147 188 $count = time();
148 189 }
149 190 $slug = $title . '-' . $count;
150 - $count++;
191 + ++$count;
151 192 }
152 193
153 194 if (strlen($slug) <= 4) {
154 195 $slug = $slug . '-' . time();
@@ -159,9 +200,9 @@
159 200
160 201 protected static function getDefaultMeta()
161 202 {
162 203 return [
163 - 'preview_data' => null
204 + 'preview_data' => null,
164 205 ];
165 206 }
166 207
167 208 public function setMetaAttribute($value)
@@ -176,9 +217,9 @@
176 217 if (!$meta) {
177 218 $meta = [];
178 219 }
179 220
180 - return $meta;
221 + return Helper::sanitizeStoredMediaPreview($meta);
181 222 }
182 223
183 224 public function scopeSearchBy($query, $search, $in = [])
184 225 {
@@ -186,9 +227,9 @@
186 227 return $query;
187 228 }
188 229
189 230 if (!$in || !is_array($in)) {
190 - $in = ['post_content'];
231 + $in = [ 'post_content' ];
191 232 }
192 233
193 234 $fields = $this->searchable;
194 235 $query->where(function ($query) use ($fields, $search, $in) {
@@ -207,9 +248,9 @@
207 248 $query->orWhereHas('comments', function ($q) use ($search) {
208 249 return $q->where('message', 'LIKE', "%$search%");
209 250 });
210 251 }
211 - } else if ($in && in_array('post_comments', $in)) {
252 + } elseif ($in && in_array('post_comments', $in)) {
212 253 $query->whereHas('comments', function ($q) use ($search) {
213 254 return $q->where('message', 'LIKE', "%$search%");
214 255 });
215 256 }
@@ -251,16 +292,15 @@
251 292 if (
252 293 $user->hasCommunityModeratorAccess() ||
253 294 ($space && $user->hasSpacePermission('edit_any_feed', $space))
254 295 ) {
255 - return $query->whereIn('status', ['published', 'pending']);
296 + return $query->whereIn('status', [ 'published', 'pending' ]);
256 297 }
257 298
258 - // This is a normal User.
259 - return $query->where(function ($q) use ($user) {
260 - $q->where('status', 'published')
261 - ->orWhere(function ($q) use ($user) {
262 - $q->where('status', 'pending')
299 + return $query->where(function ($statusQuery) use ($user) {
300 + $statusQuery->where('status', 'published')
301 + ->orWhere(function ($subQuery) use ($user) {
302 + $subQuery->where('status', 'pending')
263 303 ->where('user_id', $user->ID);
264 304 });
265 305 });
266 306 }
@@ -419,8 +459,44 @@
419 459 return $this->hasMany(Reaction::class, 'object_id', 'id')
420 460 ->where('object_type', 'feed');
421 461 }
422 462
463 + /**
464 + * Eager-load closures for rendering a feed with its public relations
465 + * (author, moderation-scoped comments, space, top reactions, topics).
466 + */
467 + public static function withPublicRelations($currentUserModel, $space = null)
468 + {
469 + return [
470 + 'xprofile' => function ($q) {
471 + $q->select(ProfileHelper::getXProfilePublicFields());
472 + },
473 + 'comments' => function ($q) use ($currentUserModel, $space) {
474 + $q->byContentModerationAccessStatus($currentUserModel, $space)
475 + ->with(['xprofile' => function ($q) {
476 + $q->select(ProfileHelper::getXProfilePublicFields());
477 + }])
478 + ->whereHas('xprofile', function ($q) {
479 + $q->where('status', 'active');
480 + });
481 + },
482 + 'space' => function ($q) {
483 + $q->select(['id', 'title', 'slug', 'type', 'settings']);
484 + },
485 + 'reactions' => function ($q) {
486 + $q->with(['xprofile' => function ($query) {
487 + $query->select(['user_id', 'avatar', 'display_name']);
488 + }])
489 + ->where('type', 'like')
490 + ->limit(3);
491 + },
492 + 'terms' => function ($q) {
493 + $q->select(['title', 'slug'])
494 + ->where('taxonomy_name', 'post_topic');
495 + }
496 + ];
497 + }
498 +
423 499 // New Relationship: Follow records where this post's user_id is the followed_id
424 500 public function follows()
425 501 {
426 502 return $this->hasMany(Follow::class, 'followed_id', 'user_id');
@@ -442,9 +518,9 @@
442 518 if (!$userId) {
443 519 return false;
444 520 }
445 521
446 - return Reaction::select(['id'])
522 + return Reaction::select([ 'id' ])
447 523 ->where('object_id', $this->id)
448 524 ->where('object_type', 'feed')
449 525 ->where('user_id', $userId)
450 526 ->where('type', $type)
@@ -555,9 +631,9 @@
555 631 Meta::create([
556 632 'object_id' => $this->id,
557 633 'object_type' => 'feed',
558 634 'meta_key' => $key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
559 - 'value' => $value
635 + 'value' => $value,
560 636 ]);
561 637 }
562 638
563 639 return true;
@@ -608,10 +684,10 @@
608 684 return [
609 685 'name' => 'view_lesson',
610 686 'params' => [
611 687 'course_slug' => $this->space ? $this->space->slug : 'uknown',
612 - 'lesson_slug' => $this->slug
613 - ]
688 + 'lesson_slug' => $this->slug,
689 + ],
614 690 ];
615 691 }
616 692
617 693 if ($this->space_id) {
@@ -618,17 +694,17 @@
618 694 $route = [
619 695 'name' => 'space_feed',
620 696 'params' => [
621 697 'space' => $this->space->slug,
622 - 'feed_slug' => $this->slug
623 - ]
698 + 'feed_slug' => $this->slug,
699 + ],
624 700 ];
625 701 } else {
626 702 $route = [
627 703 'name' => 'single_feed',
628 704 'params' => [
629 - 'feed_slug' => $this->slug
630 - ]
705 + 'feed_slug' => $this->slug,
706 + ],
631 707 ];
632 708 }
633 709
634 710 return $route;
@@ -665,9 +741,9 @@
665 741
666 742 $feedHtml = $this->message_rendered;
667 743 $feedHtml .= FeedsHelper::getMediaHtml($this->meta, $postPermalink);
668 744
669 - $buttonText = $buttonText ?: __('Join the conversation', 'fluent-community');
745 + $buttonText = $buttonText ? $buttonText : __('Join the conversation', 'fluent-community');
670 746
671 747 $emailComposer->addBlock('post_boxed_content', $feedHtml, [
672 748 'user' => $this->user,
673 749 'title' => $this->title,
@@ -672,13 +748,13 @@
672 748 'user' => $this->user,
673 749 'title' => $this->title,
674 750 'permalink' => $postPermalink,
675 751 'space_name' => $this->space ? $this->space->title : __('Community', 'fluent-community'),
676 - 'is_single' => true
752 + 'is_single' => true,
677 753 ]);
678 754
679 755 $emailComposer->addBlock('button', $buttonText, [
680 - 'link' => $postPermalink
756 + 'link' => $postPermalink,
681 757 ]);
682 758
683 759 $emailComposer->setDefaultLogo();
684 760