PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.7.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.7.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
fluent-community / app / Models / Feed.php

Feed.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.7.0, at app/Models/Feed.php

690 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\App\Models;
4
5 use FluentCommunity\App\Functions\Utility;
6 use FluentCommunity\App\Services\FeedsHelper;
7 use FluentCommunity\App\Services\Helper;
8 use FluentCommunityPro\App\Models\Follow;
9
10 class Feed extends Model
11 {
12 protected $table = 'fcom_posts';
13
14 protected $guarded = ['id'];
15
16 protected $casts = [
17 'comments_count' => 'int',
18 'reactions_count' => 'int',
19 'is_sticky' => 'int',
20 'priority' => 'int',
21 ];
22
23 protected $fillable = [
24 'user_id',
25 'title',
26 'slug',
27 'message',
28 'message_rendered',
29 'type',
30 'content_type',
31 'space_id',
32 'privacy',
33 'status',
34 'priority',
35 'featured_image',
36 'is_sticky',
37 'expired_at',
38 'scheduled_at',
39 'comments_count',
40 'reactions_count',
41 'meta',
42 'created_at',
43 'updated_at'
44 ];
45
46 protected $searchable = [
47 'message',
48 'title'
49 ];
50
51 public static $publicColumns = [
52 'id',
53 'slug',
54 'message_rendered',
55 'meta',
56 'title',
57 'featured_image',
58 'created_at',
59 'privacy',
60 'priority',
61 'type',
62 'content_type',
63 'slug',
64 'space_id',
65 'user_id',
66 'status',
67 'is_sticky',
68 'scheduled_at',
69 'comments_count',
70 'reactions_count'
71 ];
72
73 protected $appends = [
74 'permalink'
75 ];
76
77 public static $scopeType = 'text';
78
79 public static function boot()
80 {
81 parent::boot();
82
83 static::creating(function ($model) {
84 if (empty($model->user_id)) {
85 $model->user_id = get_current_user_id();
86 }
87 if (empty($model->slug)) {
88 $model->slug = self::generateNewSlug($model);
89 }
90
91 if (empty($model->meta)) {
92 $model->meta = self::getDefaultMeta();
93 }
94
95 if (empty($model->status)) {
96 $model->status = 'published';
97 }
98
99 });
100
101 static::addGlobalScope('type', function ($builder) {
102 $builder->where('type', self::$scopeType);
103 });
104
105 static::deleting(function ($feed) {
106 Media::where('feed_id', $feed->id)
107 ->update([
108 'is_active' => 0
109 ]);
110 Reaction::where('object_id', $feed->id)
111 ->where(function ($query) {
112 $query->where('object_type', 'feed')
113 ->orWhere('type', 'survey_vote');
114 })
115 ->delete();
116 });
117
118 }
119
120 protected static function generateNewSlug($newModel)
121 {
122 if ($newModel->title) {
123 // Remove the emojis
124 $title = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $newModel->title);
125 // get the first 40 char from the title
126 $title = mb_substr($title, 0, 40, 'UTF-8');
127 } else {
128 // get the first 25 char from the message
129 $title = mb_substr($newModel->message, 0, 40, 'UTF-8');
130 }
131
132 $title = Helper::normalizeToAscii($title);
133 $title = remove_accents($title);
134
135 $title = strtolower($title);
136 // only allow alphanumeric, dash, and underscore
137 $title = trim(preg_replace('/[^a-z0-9-_]/', ' ', $title));
138
139 $title = sanitize_title($title, 'post-' . time());
140
141 // check if the slug is already exists
142 $slug = $title;
143
144 $count = 1;
145 while (self::where('slug', $slug)->exists()) {
146 if ($count == 5) {
147 $count = time();
148 }
149 $slug = $title . '-' . $count;
150 $count++;
151 }
152
153 if (strlen($slug) <= 4) {
154 $slug = $slug . '-' . time();
155 }
156
157 return $slug;
158 }
159
160 protected static function getDefaultMeta()
161 {
162 return [
163 'preview_data' => null
164 ];
165 }
166
167 public function setMetaAttribute($value)
168 {
169 $this->attributes['meta'] = maybe_serialize($value);
170 }
171
172 public function getMetaAttribute($value)
173 {
174 $meta = Utility::safeUnserialize($value);
175
176 if (!$meta) {
177 $meta = [];
178 }
179
180 return $meta;
181 }
182
183 public function scopeSearchBy($query, $search, $in = [])
184 {
185 if (!$search) {
186 return $query;
187 }
188
189 if (!$in || !is_array($in)) {
190 $in = ['post_content'];
191 }
192
193 $fields = $this->searchable;
194 $query->where(function ($query) use ($fields, $search, $in) {
195 if (in_array('post_content', $in)) {
196 $query->where(array_shift($fields), 'LIKE', "%$search%");
197 foreach ($fields as $field) {
198 $query->orWhere($field, 'LIKE', "%$search%");
199 }
200
201 $query->orWhere(function ($q) use ($search) {
202 $q->where('content_type', 'document')
203 ->where('meta', 'LIKE', '%document_lists%title%' . $search . '%');
204 });
205
206 if ($in && in_array('post_comments', $in)) {
207 $query->orWhereHas('comments', function ($q) use ($search) {
208 return $q->where('message', 'LIKE', "%$search%");
209 });
210 }
211 } else if ($in && in_array('post_comments', $in)) {
212 $query->whereHas('comments', function ($q) use ($search) {
213 return $q->where('message', 'LIKE', "%$search%");
214 });
215 }
216 });
217
218 return $query;
219 }
220
221 public function scopeByUserAccess($query, $userId)
222 {
223 if ($userId) {
224 return $query->where(function ($subQuery) use ($userId) {
225 $subQuery->where('user_id', $userId)->orWhereNull('space_id')
226 ->orWhereHas('space', function ($q) use ($userId) {
227 $spaceIds = get_user_meta($userId, '_fcom_space_ids', true);
228 if ($spaceIds) {
229 $q->whereIn('id', $spaceIds);
230 return $q;
231 }
232 return $q->where('privacy', 'public');
233 });
234 });
235 }
236
237 return $query->where(function ($q) {
238 $q->whereNull('space_id')
239 ->orWhereHas('space', function ($q) {
240 $q->where('privacy', 'public');
241 });
242 });
243 }
244
245 public function scopeByContentModerationAccessStatus($query, $user, $space = null)
246 {
247 if (!$user || !Helper::isFeatureEnabled('content_moderation')) {
248 return $query->where('status', 'published');
249 }
250
251 if (
252 $user->hasCommunityModeratorAccess() ||
253 ($space && $user->hasSpacePermission('edit_any_feed', $space))
254 ) {
255 return $query->whereIn('status', ['published', 'pending']);
256 }
257
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')
263 ->where('user_id', $user->ID);
264 });
265 });
266 }
267
268 public function scopeByBookMarked($query, $userId)
269 {
270 return $query->whereHas('reactions', function ($q) use ($userId) {
271 $q->where('user_id', $userId)
272 ->where('type', 'bookmark');
273 });
274 }
275
276 public function scopeByTopicSlug($query, $topicSlug)
277 {
278 if (!$topicSlug) {
279 return $query;
280 }
281
282 return $query->whereHas('terms', function ($q) use ($topicSlug) {
283 $topic = Term::where('taxonomy_name', 'post_topic')->where('slug', $topicSlug)->first();
284 if ($topic) {
285 $q->where('term_id', $topic->id);
286 }
287 });
288 }
289
290 public function scopeFilterBySpaceSlug($query, $space)
291 {
292 if (!$space) {
293 return $query;
294 }
295
296 $query->whereHas('space', function ($q) use ($space) {
297 $q->where('slug', $space);
298 });
299
300 return $query;
301 }
302
303 public function scopeByType($query, $type)
304 {
305 if (!$type) {
306 return $query;
307 }
308
309 $query->where('type', $type);
310
311 return $query;
312 }
313
314 public function scopeCustomOrderBy($query, $type)
315 {
316 $acceptedTypes = array_keys(Helper::getPostOrderOptions());
317
318 if (!in_array($type, $acceptedTypes) || $type == 'latest') {
319 return $query->orderBy('created_at', 'DESC');
320 }
321
322 if ($type == 'new_activity') {
323 return $query->orderBy('updated_at', 'DESC');
324 }
325
326 if ($type == 'oldest') {
327 return $query->orderBy('created_at', 'ASC');
328 }
329
330 if ($type == 'likes') {
331 return $query->orderBy('reactions_count', 'DESC');
332 }
333
334 if ($type == 'unanswered') {
335 return $query->where('comments_count', 0)
336 ->orderBy('created_at', 'DESC');
337 }
338
339 if ($type == 'alphabetical') {
340 return $query->orderBy('slug', 'ASC');
341 }
342
343 if ($type == 'popular') {
344 // sort by comments_count + reactions_count desc
345 return $query->orderByRaw('(reactions_count + (comments_count * 2)) DESC');
346 }
347
348 $query = apply_filters('fluent_community/custom_order_by', $query, $type);
349
350 return $query;
351 }
352
353 public function scopeByStatus($query, $status)
354 {
355 if (!$status) {
356 return $query->where('status', 'published');
357 }
358
359 $query->where('status', $status);
360
361 return $query;
362 }
363
364 public function scopeByFollowing($query, $userId = null)
365 {
366 $query->orderBy('updated_at', 'DESC');
367
368 if (!Helper::isFeatureEnabled('followers_module')) {
369 return $query;
370 }
371
372 if (!$userId) {
373 $userId = get_current_user_id();
374 }
375
376 if (!$userId) {
377 return $query;
378 }
379
380 return $query->whereHas('follows', function ($query) use ($userId) {
381 $query->where('follower_id', $userId);
382 });
383 }
384
385 public function scopeFilterByUserId($query, $userId)
386 {
387 if (!$userId) {
388 return $query;
389 }
390
391 $query->where('user_id', $userId);
392
393 return $query;
394 }
395
396 public function user()
397 {
398 return $this->belongsTo(User::class, 'user_id', 'ID');
399 }
400
401 public function xprofile()
402 {
403 return $this->belongsTo(XProfile::class, 'user_id', 'user_id');
404 }
405
406 public function space()
407 {
408 return $this->belongsTo(BaseSpace::class, 'space_id', 'id')
409 ->withoutGlobalScopes();
410 }
411
412 public function comments()
413 {
414 return $this->hasMany(Comment::class, 'post_id', 'id');
415 }
416
417 public function reactions()
418 {
419 return $this->hasMany(Reaction::class, 'object_id', 'id')
420 ->where('object_type', 'feed');
421 }
422
423 // New Relationship: Follow records where this post's user_id is the followed_id
424 public function follows()
425 {
426 return $this->hasMany(Follow::class, 'followed_id', 'user_id');
427 }
428
429 public function surveyVotes()
430 {
431 return $this->hasMany(Reaction::class, 'object_id', 'id')
432 ->where('type', 'survey_vote');
433 }
434
435 public function terms()
436 {
437 return $this->belongsToMany(Term::class, 'fcom_term_feed', 'post_id', 'term_id');
438 }
439
440 public function hasUserReact($userId, $type = 'like')
441 {
442 if (!$userId) {
443 return false;
444 }
445
446 return Reaction::select(['id'])
447 ->where('object_id', $this->id)
448 ->where('object_type', 'feed')
449 ->where('user_id', $userId)
450 ->where('type', $type)
451 ->exists();
452 }
453
454 public function hasEditAccess($userId)
455 {
456 if (!$userId) {
457 return false;
458 }
459
460 if ($this->user_id == $userId) {
461 return true;
462 }
463
464 $userModel = User::find($userId);
465
466 if (!$userModel) {
467 return false;
468 }
469
470 if ($this->space_id) {
471 return $userModel->hasSpacePermission('edit_any_feed', $this->space);
472 }
473
474 return $userModel->hasCommunityPermission('edit_any_feed');
475 }
476
477 public function getHumanExcerpt($length = 40)
478 {
479 $content = $this->title;
480 if (!$content) {
481 $content = $this->message;
482 }
483
484 return Helper::getHumanExcerpt($content, $length);
485 }
486
487 public function getPermalink()
488 {
489 $sectionPrefix = 'space';
490 $contentPrefix = 'post';
491 $isLesson = $this->type === 'course_lesson';
492
493 if ($isLesson) {
494 $sectionPrefix = 'course';
495 $contentPrefix = 'lessons';
496 }
497
498 $urlPath = $contentPrefix . '/' . $this->slug;
499
500 if ($this->space_id && $this->space) {
501 $urlPath = $sectionPrefix . '/' . $this->space->slug . '/' . $contentPrefix . '/' . $this->slug;
502 if ($isLesson) {
503 $urlPath .= '/view';
504 }
505 }
506
507 return Helper::baseUrl($urlPath);
508 }
509
510 public function getPermalinkAttribute()
511 {
512 return $this->getPermalink();
513 }
514
515 public function activities()
516 {
517 return $this->hasMany(Activity::class, 'feed_id', 'id');
518 }
519
520 public function notifications()
521 {
522 return $this->hasMany(Notification::class, 'feed_id', 'id');
523 }
524
525 public function media()
526 {
527 return $this->hasMany(Media::class, 'feed_id', 'id');
528 }
529
530 public function getSurveyCastsByUserId($userId = null)
531 {
532 if (!$userId || $this->content_type != 'survey') {
533 return [];
534 }
535
536 return Utility::getFromCache('survey_cast_' . $this->id . '_' . $userId, function () use ($userId) {
537 return Reaction::where('type', 'survey_vote')
538 ->where('user_id', $userId)
539 ->where('object_id', $this->id)
540 ->pluck('object_type')->toArray();
541 }, 86400);
542 }
543
544 public function updateCustomMeta($key, $value)
545 {
546 $exist = Meta::where('object_id', $this->id)
547 ->where('object_type', 'feed')
548 ->where('meta_key', $key)
549 ->first();
550
551 if ($exist) {
552 $exist->value = $value;
553 $exist->save();
554 } else {
555 Meta::create([
556 'object_id' => $this->id,
557 'object_type' => 'feed',
558 'meta_key' => $key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
559 'value' => $value
560 ]);
561 }
562
563 return true;
564 }
565
566 public function getCustomMeta($key, $default = null)
567 {
568 $exist = Meta::where('object_id', $this->id)
569 ->where('object_type', 'feed')
570 ->where('meta_key', $key)
571 ->first();
572
573 if ($exist) {
574 return $exist->value;
575 }
576
577 return $default;
578 }
579
580 public function attachTopics($topicIds, $sync = false)
581 {
582 if ((!$topicIds && !$sync) || !$this->space_id) {
583 return $this;
584 }
585
586 // let's find the valid topics for this space
587 $spaceTopics = Utility::getTopicsBySpaceId($this->space_id);
588 $spaceTopicIds = array_map(function ($topic) {
589 return $topic['id'];
590 }, $spaceTopics);
591
592 $validTopicIds = array_filter($topicIds, function ($topicId) use ($spaceTopicIds) {
593 return in_array($topicId, $spaceTopicIds);
594 });
595
596 if ($sync) {
597 $this->terms()->sync($validTopicIds);
598 } else {
599 $this->terms()->attach($validTopicIds);
600 }
601
602 return $this;
603 }
604
605 public function getJsRoute()
606 {
607 if ($this->type == 'course_lesson') {
608 return [
609 'name' => 'view_lesson',
610 'params' => [
611 'course_slug' => $this->space ? $this->space->slug : 'uknown',
612 'lesson_slug' => $this->slug
613 ]
614 ];
615 }
616
617 if ($this->space_id) {
618 $route = [
619 'name' => 'space_feed',
620 'params' => [
621 'space' => $this->space->slug,
622 'feed_slug' => $this->slug
623 ]
624 ];
625 } else {
626 $route = [
627 'name' => 'single_feed',
628 'params' => [
629 'feed_slug' => $this->slug
630 ]
631 ];
632 }
633
634 return $route;
635 }
636
637 public function recountStats()
638 {
639 $this->comments_count = Comment::where('post_id', $this->id)
640 ->where('type', 'comment')
641 ->count();
642
643 $this->reactions_count = Reaction::where('object_type', 'feed')->where('type', 'like')
644 ->where('object_id', $this->id)
645 ->count();
646
647 $this->save();
648 return $this;
649 }
650
651 public function isEnabledForEveryoneTag()
652 {
653 return ($this->meta['send_announcement_email'] ?? null) === 'yes' && Utility::hasEmailAnnouncementEnabled();
654 }
655
656 public function getFeedHtml($withPlaceholder = false, $buttonText = null)
657 {
658 $emailComposer = new \FluentCommunity\App\Services\Libs\EmailComposer();
659
660 if ($withPlaceholder) {
661 $postPermalink = '##feed_permalink##';
662 } else {
663 $postPermalink = $this->getPermalink();
664 }
665
666 $feedHtml = $this->message_rendered;
667 $feedHtml .= FeedsHelper::getMediaHtml($this->meta, $postPermalink);
668
669 $buttonText = $buttonText ?: __('Join the conversation', 'fluent-community');
670
671 $emailComposer->addBlock('post_boxed_content', $feedHtml, [
672 'user' => $this->user,
673 'title' => $this->title,
674 'permalink' => $postPermalink,
675 'space_name' => $this->space ? $this->space->title : __('Community', 'fluent-community'),
676 'is_single' => true
677 ]);
678
679 $emailComposer->addBlock('button', $buttonText, [
680 'link' => $postPermalink
681 ]);
682
683 $emailComposer->setDefaultLogo();
684
685 $emailComposer->setDefaultFooter();
686
687 return $emailComposer->getHtml();
688 }
689 }
690