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

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