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 +120 -38 2.6.012.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,11 +145,15 @@
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)
152 + ->where(function ($query) {
153 + $query->where('object_type', 'feed')
154 + ->orWhere('type', 'survey_vote');
155 + })
111 156 ->delete();
112 157 });
113 158
114 159 }
@@ -121,9 +166,9 @@
121 166 // get the first 40 char from the title
122 167 $title = mb_substr($title, 0, 40, 'UTF-8');
123 168 } else {
124 169 // get the first 25 char from the message
125 - $title = mb_substr($newModel->message, 0, 40, 'UTF-8');
170 + $title = mb_substr((string) $newModel->message, 0, 40, 'UTF-8');
126 171 }
127 172
128 173 $title = Helper::normalizeToAscii($title);
129 174 $title = remove_accents($title);
@@ -142,9 +187,9 @@
142 187 if ($count == 5) {
143 188 $count = time();
144 189 }
145 190 $slug = $title . '-' . $count;
146 - $count++;
191 + ++$count;
147 192 }
148 193
149 194 if (strlen($slug) <= 4) {
150 195 $slug = $slug . '-' . time();
@@ -155,9 +200,9 @@
155 200
156 201 protected static function getDefaultMeta()
157 202 {
158 203 return [
159 - 'preview_data' => null
204 + 'preview_data' => null,
160 205 ];
161 206 }
162 207
163 208 public function setMetaAttribute($value)
@@ -172,9 +217,9 @@
172 217 if (!$meta) {
173 218 $meta = [];
174 219 }
175 220
176 - return $meta;
221 + return Helper::sanitizeStoredMediaPreview($meta);
177 222 }
178 223
179 224 public function scopeSearchBy($query, $search, $in = [])
180 225 {
@@ -182,9 +227,9 @@
182 227 return $query;
183 228 }
184 229
185 230 if (!$in || !is_array($in)) {
186 - $in = ['post_content'];
231 + $in = [ 'post_content' ];
187 232 }
188 233
189 234 $fields = $this->searchable;
190 235 $query->where(function ($query) use ($fields, $search, $in) {
@@ -203,9 +248,9 @@
203 248 $query->orWhereHas('comments', function ($q) use ($search) {
204 249 return $q->where('message', 'LIKE', "%$search%");
205 250 });
206 251 }
207 - } else if ($in && in_array('post_comments', $in)) {
252 + } elseif ($in && in_array('post_comments', $in)) {
208 253 $query->whereHas('comments', function ($q) use ($search) {
209 254 return $q->where('message', 'LIKE', "%$search%");
210 255 });
211 256 }
@@ -216,17 +261,19 @@
216 261
217 262 public function scopeByUserAccess($query, $userId)
218 263 {
219 264 if ($userId) {
220 - return $query->where('user_id', $userId)->orWhereNull('space_id')
221 - ->orWhereHas('space', function ($q) use ($userId) {
222 - $spaceIds = get_user_meta($userId, '_fcom_space_ids', true);
223 - if ($spaceIds) {
224 - $q->whereIn('id', $spaceIds);
225 - return $q;
226 - }
227 - return $q->where('privacy', 'public');
228 - });
265 + return $query->where(function ($subQuery) use ($userId) {
266 + $subQuery->where('user_id', $userId)->orWhereNull('space_id')
267 + ->orWhereHas('space', function ($q) use ($userId) {
268 + $spaceIds = get_user_meta($userId, '_fcom_space_ids', true);
269 + if ($spaceIds) {
270 + $q->whereIn('id', $spaceIds);
271 + return $q;
272 + }
273 + return $q->where('privacy', 'public');
274 + });
275 + });
229 276 }
230 277
231 278 return $query->where(function ($q) {
232 279 $q->whereNull('space_id')
@@ -245,16 +292,15 @@
245 292 if (
246 293 $user->hasCommunityModeratorAccess() ||
247 294 ($space && $user->hasSpacePermission('edit_any_feed', $space))
248 295 ) {
249 - return $query->whereIn('status', ['published', 'pending']);
296 + return $query->whereIn('status', [ 'published', 'pending' ]);
250 297 }
251 298
252 - // This is a normal User.
253 - return $query->where(function ($q) use ($user) {
254 - $q->where('status', 'published')
255 - ->orWhere(function ($q) use ($user) {
256 - $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')
257 303 ->where('user_id', $user->ID);
258 304 });
259 305 });
260 306 }
@@ -413,8 +459,44 @@
413 459 return $this->hasMany(Reaction::class, 'object_id', 'id')
414 460 ->where('object_type', 'feed');
415 461 }
416 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 +
417 499 // New Relationship: Follow records where this post's user_id is the followed_id
418 500 public function follows()
419 501 {
420 502 return $this->hasMany(Follow::class, 'followed_id', 'user_id');
@@ -436,9 +518,9 @@
436 518 if (!$userId) {
437 519 return false;
438 520 }
439 521
440 - return Reaction::select(['id'])
522 + return Reaction::select([ 'id' ])
441 523 ->where('object_id', $this->id)
442 524 ->where('object_type', 'feed')
443 525 ->where('user_id', $userId)
444 526 ->where('type', $type)
@@ -549,9 +631,9 @@
549 631 Meta::create([
550 632 'object_id' => $this->id,
551 633 'object_type' => 'feed',
552 634 'meta_key' => $key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
553 - 'value' => $value
635 + 'value' => $value,
554 636 ]);
555 637 }
556 638
557 639 return true;
@@ -602,10 +684,10 @@
602 684 return [
603 685 'name' => 'view_lesson',
604 686 'params' => [
605 687 'course_slug' => $this->space ? $this->space->slug : 'uknown',
606 - 'lesson_slug' => $this->slug
607 - ]
688 + 'lesson_slug' => $this->slug,
689 + ],
608 690 ];
609 691 }
610 692
611 693 if ($this->space_id) {
@@ -612,17 +694,17 @@
612 694 $route = [
613 695 'name' => 'space_feed',
614 696 'params' => [
615 697 'space' => $this->space->slug,
616 - 'feed_slug' => $this->slug
617 - ]
698 + 'feed_slug' => $this->slug,
699 + ],
618 700 ];
619 701 } else {
620 702 $route = [
621 703 'name' => 'single_feed',
622 704 'params' => [
623 - 'feed_slug' => $this->slug
624 - ]
705 + 'feed_slug' => $this->slug,
706 + ],
625 707 ];
626 708 }
627 709
628 710 return $route;
@@ -659,9 +741,9 @@
659 741
660 742 $feedHtml = $this->message_rendered;
661 743 $feedHtml .= FeedsHelper::getMediaHtml($this->meta, $postPermalink);
662 744
663 - $buttonText = $buttonText ?: __('Join the conversation', 'fluent-community');
745 + $buttonText = $buttonText ? $buttonText : __('Join the conversation', 'fluent-community');
664 746
665 747 $emailComposer->addBlock('post_boxed_content', $feedHtml, [
666 748 'user' => $this->user,
667 749 'title' => $this->title,
@@ -666,13 +748,13 @@
666 748 'user' => $this->user,
667 749 'title' => $this->title,
668 750 'permalink' => $postPermalink,
669 751 'space_name' => $this->space ? $this->space->title : __('Community', 'fluent-community'),
670 - 'is_single' => true
752 + 'is_single' => true,
671 753 ]);
672 754
673 755 $emailComposer->addBlock('button', $buttonText, [
674 - 'link' => $postPermalink
756 + 'link' => $postPermalink,
675 757 ]);
676 758
677 759 $emailComposer->setDefaultLogo();
678 760