# fluent-community/2.7.7/app/Models/Feed.php

FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS &amp; Online Courses, version 2.7.7. 756 lines.

- Page: https://pluginprobe.com/plugins/fluent-community/2.7.7/code/app/Models/Feed.php
- Raw: https://pluginprobe.com/plugins/fluent-community/2.7.7/raw/app/Models/Feed.php
- Modified: 2026-08-10T11:44:38+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-community/2.7.7/code/app/Models/Feed.php#L10-L20`.

```php
<?php

namespace FluentCommunity\App\Models;

use FluentCommunity\App\Functions\Utility;
use FluentCommunity\App\Services\FeedsHelper;
use FluentCommunity\App\Services\Helper;
use FluentCommunity\App\Services\ProfileHelper;
use FluentCommunityPro\App\Models\Follow;

/**
 * @property int             $id
 * @property int|null        $user_id
 * @property string|null     $title
 * @property string|null     $slug
 * @property string|null     $message
 * @property string|null     $message_rendered
 * @property string|null     $type
 * @property string|null     $content_type
 * @property int|null        $space_id
 * @property string|null     $privacy
 * @property string|null     $status
 * @property int|null        $priority
 * @property string|null     $featured_image
 * @property int|null        $is_sticky
 * @property string|null     $expired_at
 * @property string|null     $scheduled_at
 * @property int|null        $comments_count
 * @property int|null        $reactions_count
 * @property array           $meta
 * @property string|null     $created_at
 * @property string|null     $updated_at
 * @property-read User|null       $user
 * @property-read BaseSpace|null  $space
 * @property-read \FluentCommunity\Framework\Database\Orm\Collection $comments
 * @property bool|null            $has_user_react
 * @property bool|null            $bookmarked
 * @property string|null          $default_comment_sort_by
 */
class Feed extends Model
{
    protected $table = 'fcom_posts';

    protected $guarded = [ 'id' ];

    protected $casts = [
        'comments_count'  => 'int',
        'reactions_count' => 'int',
        'is_sticky'       => 'int',
        'priority'        => 'int',
    ];

    protected $fillable = [
        'user_id',
        'title',
        'slug',
        'message',
        'message_rendered',
        'type',
        'content_type',
        'space_id',
        'privacy',
        'status',
        'priority',
        'featured_image',
        'is_sticky',
        'expired_at',
        'scheduled_at',
        'comments_count',
        'reactions_count',
        'meta',
        'created_at',
        'updated_at',
    ];

    protected $searchable = [
        'message',
        'title',
    ];

    public static $publicColumns = [
        'id',
        'slug',
        'message_rendered',
        'meta',
        'title',
        'featured_image',
        'created_at',
        'privacy',
        'priority',
        'type',
        'content_type',
        'slug',
        'space_id',
        'user_id',
        'status',
        'is_sticky',
        'scheduled_at',
        'comments_count',
        'reactions_count',
    ];

    protected $appends = [
        'permalink',
    ];

    public static $scopeType = 'text';

    public static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            if (empty($model->user_id)) {
                $model->user_id = get_current_user_id();
            }
            if (empty($model->slug)) {
                $model->slug = self::generateNewSlug($model);
            }

            if (empty($model->meta)) {
                $model->meta = self::getDefaultMeta();
            }

            if (empty($model->status)) {
                $model->status = 'published';
            }

        });

        static::addGlobalScope('type', function ($builder) {
            $builder->where('type', self::$scopeType);
        });

        static::deleting(function ($feed) {
            Media::where('feed_id', $feed->id)
                ->update([
                    'is_active' => 0,
                ]);
            Reaction::where('object_id', $feed->id)
                ->where(function ($query) {
                    $query->where('object_type', 'feed')
                        ->orWhere('type', 'survey_vote');
                })
                ->delete();
        });

    }

    protected static function generateNewSlug($newModel)
    {
        if ($newModel->title) {
            // Remove the emojis
            $title = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $newModel->title);
            // get the first 40 char from the title
            $title = mb_substr($title, 0, 40, 'UTF-8');
        } else {
            // get the first 25 char from the message
            $title = mb_substr($newModel->message, 0, 40, 'UTF-8');
        }

        $title = Helper::normalizeToAscii($title);
        $title = remove_accents($title);

        $title = strtolower($title);
        // only allow alphanumeric, dash, and underscore
        $title = trim(preg_replace('/[^a-z0-9-_]/', ' ', $title));

        $title = sanitize_title($title, 'post-' . time());

        // check if the slug is already exists
        $slug = $title;

        $count = 1;
        while (self::where('slug', $slug)->exists()) {
            if ($count == 5) {
                $count = time();
            }
            $slug = $title . '-' . $count;
            ++$count;
        }

        if (strlen($slug) <= 4) {
            $slug = $slug . '-' . time();
        }

        return $slug;
    }

    protected static function getDefaultMeta()
    {
        return [
            'preview_data' => null,
        ];
    }

    public function setMetaAttribute($value)
    {
        $this->attributes['meta'] = maybe_serialize($value);
    }

    public function getMetaAttribute($value)
    {
        $meta = Utility::safeUnserialize($value);

        if (!$meta) {
            $meta = [];
        }

        return Helper::sanitizeStoredMediaPreview($meta);
    }

    public function scopeSearchBy($query, $search, $in = [])
    {
        if (!$search) {
            return $query;
        }

        if (!$in || !is_array($in)) {
            $in = [ 'post_content' ];
        }

        $fields = $this->searchable;
        $query->where(function ($query) use ($fields, $search, $in) {
            if (in_array('post_content', $in)) {
                $query->where(array_shift($fields), 'LIKE', "%$search%");
                foreach ($fields as $field) {
                    $query->orWhere($field, 'LIKE', "%$search%");
                }

                $query->orWhere(function ($q) use ($search) {
                    $q->where('content_type', 'document')
                        ->where('meta', 'LIKE', '%document_lists%title%' . $search . '%');
                });

                if ($in && in_array('post_comments', $in)) {
                    $query->orWhereHas('comments', function ($q) use ($search) {
                        return $q->where('message', 'LIKE', "%$search%");
                    });
                }
            } elseif ($in && in_array('post_comments', $in)) {
                $query->whereHas('comments', function ($q) use ($search) {
                    return $q->where('message', 'LIKE', "%$search%");
                });
            }
        });

        return $query;
    }

    public function scopeByUserAccess($query, $userId)
    {
        if ($userId) {
            return $query->where(function ($subQuery) use ($userId) {
                $subQuery->where('user_id', $userId)->orWhereNull('space_id')
                    ->orWhereHas('space', function ($q) use ($userId) {
                        $spaceIds = get_user_meta($userId, '_fcom_space_ids', true);
                        if ($spaceIds) {
                            $q->whereIn('id', $spaceIds);
                            return $q;
                        }
                        return $q->where('privacy', 'public');
                    });
            });
        }

        return $query->where(function ($q) {
            $q->whereNull('space_id')
                ->orWhereHas('space', function ($q) {
                    $q->where('privacy', 'public');
                });
        });
    }

    public function scopeByContentModerationAccessStatus($query, $user, $space = null)
    {
        if (!$user || !Helper::isFeatureEnabled('content_moderation')) {
            return $query->where('status', 'published');
        }

        if (
            $user->hasCommunityModeratorAccess() ||
            ($space && $user->hasSpacePermission('edit_any_feed', $space))
        ) {
            return $query->whereIn('status', [ 'published', 'pending' ]);
        }

        // This is a normal User.
        return $query->where(function ($q) use ($user) {
            $q->where('status', 'published')
                ->orWhere(function ($q) use ($user) {
                    $q->where('status', 'pending')
                        ->where('user_id', $user->ID);
                });
        });
    }

    public function scopeByBookMarked($query, $userId)
    {
        return $query->whereHas('reactions', function ($q) use ($userId) {
            $q->where('user_id', $userId)
                ->where('type', 'bookmark');
        });
    }

    public function scopeByTopicSlug($query, $topicSlug)
    {
        if (!$topicSlug) {
            return $query;
        }

        return $query->whereHas('terms', function ($q) use ($topicSlug) {
            $topic = Term::where('taxonomy_name', 'post_topic')->where('slug', $topicSlug)->first();
            if ($topic) {
                $q->where('term_id', $topic->id);
            }
        });
    }

    public function scopeFilterBySpaceSlug($query, $space)
    {
        if (!$space) {
            return $query;
        }

        $query->whereHas('space', function ($q) use ($space) {
            $q->where('slug', $space);
        });

        return $query;
    }

    public function scopeByType($query, $type)
    {
        if (!$type) {
            return $query;
        }

        $query->where('type', $type);

        return $query;
    }

    public function scopeCustomOrderBy($query, $type)
    {
        $acceptedTypes = array_keys(Helper::getPostOrderOptions());

        if (!in_array($type, $acceptedTypes) || $type == 'latest') {
            return $query->orderBy('created_at', 'DESC');
        }

        if ($type == 'new_activity') {
            return $query->orderBy('updated_at', 'DESC');
        }

        if ($type == 'oldest') {
            return $query->orderBy('created_at', 'ASC');
        }

        if ($type == 'likes') {
            return $query->orderBy('reactions_count', 'DESC');
        }

        if ($type == 'unanswered') {
            return $query->where('comments_count', 0)
                ->orderBy('created_at', 'DESC');
        }

        if ($type == 'alphabetical') {
            return $query->orderBy('slug', 'ASC');
        }

        if ($type == 'popular') {
            // sort by comments_count + reactions_count desc
            return $query->orderByRaw('(reactions_count + (comments_count * 2)) DESC');
        }

        $query = apply_filters('fluent_community/custom_order_by', $query, $type);

        return $query;
    }

    public function scopeByStatus($query, $status)
    {
        if (!$status) {
            return $query->where('status', 'published');
        }

        $query->where('status', $status);

        return $query;
    }

    public function scopeByFollowing($query, $userId = null)
    {
        $query->orderBy('updated_at', 'DESC');

        if (!Helper::isFeatureEnabled('followers_module')) {
            return $query;
        }

        if (!$userId) {
            $userId = get_current_user_id();
        }

        if (!$userId) {
            return $query;
        }

        return $query->whereHas('follows', function ($query) use ($userId) {
            $query->where('follower_id', $userId);
        });
    }

    public function scopeFilterByUserId($query, $userId)
    {
        if (!$userId) {
            return $query;
        }

        $query->where('user_id', $userId);

        return $query;
    }

    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'ID');
    }

    public function xprofile()
    {
        return $this->belongsTo(XProfile::class, 'user_id', 'user_id');
    }

    public function space()
    {
        return $this->belongsTo(BaseSpace::class, 'space_id', 'id')
            ->withoutGlobalScopes();
    }

    public function comments()
    {
        return $this->hasMany(Comment::class, 'post_id', 'id');
    }

    public function reactions()
    {
        return $this->hasMany(Reaction::class, 'object_id', 'id')
            ->where('object_type', 'feed');
    }

    /**
     * Eager-load closures for rendering a feed with its public relations
     * (author, moderation-scoped comments, space, top reactions, topics).
     */
    public static function withPublicRelations($currentUserModel, $space = null)
    {
        return [
            'xprofile'  => function ($q) {
                $q->select(ProfileHelper::getXProfilePublicFields());
            },
            'comments'  => function ($q) use ($currentUserModel, $space) {
                $q->byContentModerationAccessStatus($currentUserModel, $space)
                    ->with(['xprofile' => function ($q) {
                        $q->select(ProfileHelper::getXProfilePublicFields());
                    }])
                    ->whereHas('xprofile', function ($q) {
                        $q->where('status', 'active');
                    });
            },
            'space'     => function ($q) {
                $q->select(['id', 'title', 'slug', 'type', 'settings']);
            },
            'reactions' => function ($q) {
                $q->with(['xprofile' => function ($query) {
                    $query->select(['user_id', 'avatar', 'display_name']);
                }])
                    ->where('type', 'like')
                    ->limit(3);
            },
            'terms'     => function ($q) {
                $q->select(['title', 'slug'])
                    ->where('taxonomy_name', 'post_topic');
            }
        ];
    }

    // New Relationship: Follow records where this post's user_id is the followed_id
    public function follows()
    {
        return $this->hasMany(Follow::class, 'followed_id', 'user_id');
    }

    public function surveyVotes()
    {
        return $this->hasMany(Reaction::class, 'object_id', 'id')
            ->where('type', 'survey_vote');
    }

    public function terms()
    {
        return $this->belongsToMany(Term::class, 'fcom_term_feed', 'post_id', 'term_id');
    }

    public function hasUserReact($userId, $type = 'like')
    {
        if (!$userId) {
            return false;
        }

        return Reaction::select([ 'id' ])
            ->where('object_id', $this->id)
            ->where('object_type', 'feed')
            ->where('user_id', $userId)
            ->where('type', $type)
            ->exists();
    }

    public function hasEditAccess($userId)
    {
        if (!$userId) {
            return false;
        }

        if ($this->user_id == $userId) {
            return true;
        }

        $userModel = User::find($userId);

        if (!$userModel) {
            return false;
        }

        if ($this->space_id) {
            return $userModel->hasSpacePermission('edit_any_feed', $this->space);
        }

        return $userModel->hasCommunityPermission('edit_any_feed');
    }

    public function getHumanExcerpt($length = 40)
    {
        $content = $this->title;
        if (!$content) {
            $content = $this->message;
        }

        return Helper::getHumanExcerpt($content, $length);
    }

    public function getPermalink()
    {
        $sectionPrefix = 'space';
        $contentPrefix = 'post';
        $isLesson = $this->type === 'course_lesson';

        if ($isLesson) {
            $sectionPrefix = 'course';
            $contentPrefix = 'lessons';
        }

        $urlPath = $contentPrefix . '/' . $this->slug;

        if ($this->space_id && $this->space) {
            $urlPath = $sectionPrefix . '/' . $this->space->slug . '/' . $contentPrefix . '/' . $this->slug;
            if ($isLesson) {
                $urlPath .= '/view';
            }
        }

        return Helper::baseUrl($urlPath);
    }

    public function getPermalinkAttribute()
    {
        return $this->getPermalink();
    }

    public function activities()
    {
        return $this->hasMany(Activity::class, 'feed_id', 'id');
    }

    public function notifications()
    {
        return $this->hasMany(Notification::class, 'feed_id', 'id');
    }

    public function media()
    {
        return $this->hasMany(Media::class, 'feed_id', 'id');
    }

    public function getSurveyCastsByUserId($userId = null)
    {
        if (!$userId || $this->content_type != 'survey') {
            return [];
        }

        return Utility::getFromCache('survey_cast_' . $this->id . '_' . $userId, function () use ($userId) {
            return Reaction::where('type', 'survey_vote')
                ->where('user_id', $userId)
                ->where('object_id', $this->id)
                ->pluck('object_type')->toArray();
        }, 86400);
    }

    public function updateCustomMeta($key, $value)
    {
        $exist = Meta::where('object_id', $this->id)
            ->where('object_type', 'feed')
            ->where('meta_key', $key)
            ->first();

        if ($exist) {
            $exist->value = $value;
            $exist->save();
        } else {
            Meta::create([
                'object_id'   => $this->id,
                'object_type' => 'feed',
                'meta_key'    => $key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
                'value'       => $value,
            ]);
        }

        return true;
    }

    public function getCustomMeta($key, $default = null)
    {
        $exist = Meta::where('object_id', $this->id)
            ->where('object_type', 'feed')
            ->where('meta_key', $key)
            ->first();

        if ($exist) {
            return $exist->value;
        }

        return $default;
    }

    public function attachTopics($topicIds, $sync = false)
    {
        if ((!$topicIds && !$sync) || !$this->space_id) {
            return $this;
        }

        // let's find the valid topics for this space
        $spaceTopics = Utility::getTopicsBySpaceId($this->space_id);
        $spaceTopicIds = array_map(function ($topic) {
            return $topic['id'];
        }, $spaceTopics);

        $validTopicIds = array_filter($topicIds, function ($topicId) use ($spaceTopicIds) {
            return in_array($topicId, $spaceTopicIds);
        });

        if ($sync) {
            $this->terms()->sync($validTopicIds);
        } else {
            $this->terms()->attach($validTopicIds);
        }

        return $this;
    }

    public function getJsRoute()
    {
        if ($this->type == 'course_lesson') {
            return [
                'name'   => 'view_lesson',
                'params' => [
                    'course_slug' => $this->space ? $this->space->slug : 'uknown',
                    'lesson_slug' => $this->slug,
                ],
            ];
        }

        if ($this->space_id) {
            $route = [
                'name'   => 'space_feed',
                'params' => [
                    'space'     => $this->space->slug,
                    'feed_slug' => $this->slug,
                ],
            ];
        } else {
            $route = [
                'name'   => 'single_feed',
                'params' => [
                    'feed_slug' => $this->slug,
                ],
            ];
        }

        return $route;
    }

    public function recountStats()
    {
        $this->comments_count = Comment::where('post_id', $this->id)
            ->where('type', 'comment')
            ->count();

        $this->reactions_count = Reaction::where('object_type', 'feed')->where('type', 'like')
            ->where('object_id', $this->id)
            ->count();

        $this->save();
        return $this;
    }

    public function isEnabledForEveryoneTag()
    {
        return ($this->meta['send_announcement_email'] ?? null) === 'yes' && Utility::hasEmailAnnouncementEnabled();
    }

    public function getFeedHtml($withPlaceholder = false, $buttonText = null)
    {
        $emailComposer = new \FluentCommunity\App\Services\Libs\EmailComposer();

        if ($withPlaceholder) {
            $postPermalink = '##feed_permalink##';
        } else {
            $postPermalink = $this->getPermalink();
        }

        $feedHtml = $this->message_rendered;
        $feedHtml .= FeedsHelper::getMediaHtml($this->meta, $postPermalink);

        $buttonText = $buttonText ? $buttonText : __('Join the conversation', 'fluent-community');

        $emailComposer->addBlock('post_boxed_content', $feedHtml, [
            'user'       => $this->user,
            'title'      => $this->title,
            'permalink'  => $postPermalink,
            'space_name' => $this->space ? $this->space->title : __('Community', 'fluent-community'),
            'is_single'  => true,
        ]);

        $emailComposer->addBlock('button', $buttonText, [
            'link' => $postPermalink,
        ]);

        $emailComposer->setDefaultLogo();

        $emailComposer->setDefaultFooter();

        return $emailComposer->getHtml();
    }
}

```
