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

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