created_by)) { $model->created_by = get_current_user_id(); } if (empty($model->slug)) { $model->slug = self::generateNewSlug($model); } $model->type = static::$type; }); static::addGlobalScope('type', function ($query) { if (static::$type) { $query->where('type', static::$type); } return $query; }); static::deleting(function ($space) { Media::where('sub_object_id', $space->id) ->whereIn('object_source', [ 'space_logo', 'space_cover_photo' ]) ->update([ 'is_active' => 0, ]); }); } public function owner() { return $this->belongsTo(User::class, 'created_by', 'ID'); } public function space_pivot() { return $this->belongsTo(SpaceUserPivot::class, 'id', 'space_id'); } public function admins() { return $this->belongsToMany(User::class, 'fcom_space_user', 'space_id', 'user_id') ->where(function ($query) { $query->where('role', 'admin') ->orWhere('role', 'moderator'); }); } public function scopeSearchBy($query, $search) { if ($search) { $fields = $this->searchable; $query->where(function ($query) use ($fields, $search) { $query->where(array_shift($fields), 'LIKE', "%$search%"); foreach ($fields as $field) { $query->orWhere($field, 'LIKE', "$search%"); } }); } return $query; } public function scopeOnlyMain($query) { return $query->withoutGlobalScopes()->whereIn('type', [ 'community', 'course' ]); } public function scopeFilterByUserId($query, $userId) { if (!$userId) { return $query->where('privacy', 'public'); } $ids = get_user_meta($userId, '_fcom_space_ids', true); if (!$ids) { return $query->where('privacy', 'public'); } return $query->whereIn('id', $ids); } public function scopeByUserAccess($query, $userId) { if (!$userId) { return $query->where('privacy', 'public'); } return $this->where(function ($query) use ($userId) { return $query->where('privacy', 'public') ->orWhereHas('members', function ($query) use ($userId) { return $query->where('user_id', $userId); }); }); } public function posts() { return $this->hasMany(Feed::class, 'space_id', 'id'); } public function comments() { return $this->hasManyThrough(Comment::class, Feed::class, 'space_id', 'post_id'); } public function members() { return $this->belongsToMany(User::class, 'fcom_space_user', 'space_id', 'user_id') ->withPivot([ 'role', 'created_at', 'status' ]); } public function x_members() { return $this->belongsToMany(XProfile::class, 'fcom_space_user', 'space_id', 'user_id', 'id', 'user_id') ->withPivot([ 'role', 'created_at', 'status' ]); } public function group() { return $this->belongsTo(SpaceGroup::class, 'parent_id', 'id'); } public function getMembership($userId) { if (!$userId) { return null; } if ($this->_preloadedMembershipUserId == $userId) { return $this->_preloadedMembership; } return $this->members()->where('user_id', $userId)->first(); } public static function preloadMemberships($spaces, $userId) { if (!$userId || $spaces->isEmpty()) { return; } $spaceIds = $spaces->pluck('id')->toArray(); $pivots = SpaceUserPivot::where('user_id', $userId) ->whereIn('space_id', $spaceIds) ->get() ->keyBy('space_id'); foreach ($spaces as $space) { $space->_preloadedMembershipUserId = $userId; $pivot = $pivots->get($space->id); $space->_preloadedMembership = $pivot ? self::pivotToMembership($pivot) : null; } } private static function pivotToMembership($pivot) { $membership = new \stdClass(); $membership->pivot = (object) $pivot->toArray(); $membership->ID = $pivot->user_id; return $membership; } public function isCourseSpace() { return $this->type == 'course'; } public function isAdmin($userId, $checkModerator = false) { if (Helper::isSiteAdmin($userId)) { return true; } $roles = [ 'admin' ]; if ($checkModerator) { if (Helper::isModerator()) { return true; } $roles[] = 'moderator'; } $membership = $this->getMembership($userId); if (!$membership) { return false; } return in_array($membership->pivot->role, $roles); } public function updateCustomData($data, $removeSrc = false) { $deletePhotos = []; if (isset($data['logo'])) { $deletePhotos[] = $this->logo; $this->logo = sanitize_url($data['logo']); } if (isset($data['cover_photo'])) { $deletePhotos[] = $this->cover_photo; $this->cover_photo = sanitize_url($data['cover_photo']); } if (isset($data['description'])) { $this->description = wp_kses_post($data['description']); } if (isset($data['title'])) { $this->title = sanitize_text_field($data['title']); } if (isset($data['privacy'])) { $this->privacy = sanitize_text_field($data['privacy']); } if (isset($data['parent_id'])) { if (!empty($data['parent_id'])) { $group = SpaceGroup::find($data['parent_id']); if (!$group) { throw new \Exception(esc_html__('Invalid group id', 'fluent-community'), 400); } $this->parent_id = $group->id; } else { $this->parent_id = null; } } if (isset($data['settings'])) { $settings = CustomSanitizer::santizeSpaceSettings($data['settings'], $this->privacy); if (is_wp_error($settings)) { return $settings; } $exisitingSetting = $this->settings; $settings['links'] = Arr::get($exisitingSetting, 'links', []); if (isset($settings['og_image'])) { $ogImageUrl = Arr::get($settings, 'og_image'); if ($ogImageUrl) { $ogImageMedia = Helper::getMediaFromUrl($ogImageUrl); if ($ogImageMedia && $ogImageMedia->is_active) { unset($settings['og_image']); } elseif ($ogImageMedia) { $ogImageMedia->update([ 'is_active' => true, 'user_id' => get_current_user_id(), 'sub_object_id' => $this->id, 'object_source' => 'space_og_media', ]); $settings['og_image'] = $ogImageMedia->public_url; } else { $settings['og_image'] = sanitize_url($ogImageUrl); } } } else { $deletePhotos[] = Arr::get($exisitingSetting, 'og_image'); } $settings = wp_parse_args($settings, $exisitingSetting); $this->settings = $settings; } if (!empty($data['slug']) && $data['slug'] !== $this->slug) { $newSlug = preg_replace('/[^a-zA-Z0-9-_]/', '', $data['slug']); $newSlug = sanitize_title($newSlug); if (empty($newSlug)) { throw new \Exception('Invalid slug', 400); } $exist = App::getInstance('db')->table('fcom_spaces')->where('slug', $newSlug) ->where('id', '!=', $this->id) ->exists(); if ($exist) { throw new \Exception(esc_html__('Slug already exists. Please use a different slug', 'fluent-community'), 400); } $this->slug = $newSlug; } $deletePhotos = array_filter($deletePhotos); if ($removeSrc && $deletePhotos) { $deletePhotos = array_filter($deletePhotos); do_action('fluent_community/remove_medias_by_url', $deletePhotos, [ 'sub_object_id' => $this->id, ]); } $dirty = $this->getDirty(); if ($dirty) { $this->save(); if ($this->type == 'community') { do_action('fluent_community/space/updated', $this, $dirty); } } return $this; } public function isContentSpace() { return in_array($this->type, [ 'community', 'course' ]); } public function getSettingsAttribute($value) { $settings = Utility::safeUnserialize($value); if (!$settings) { $settings = []; } $settings = wp_parse_args($settings, $this->defaultSettings()); if (!defined('FLUENT_COMMUNITY_PRO')) { $settings['document_library'] = 'no'; $settings['media_gallery'] = 'no'; } return $settings; } public function defaultSettings() { return [ 'restricted_post_only' => 'no', 'emoji' => '', 'shape_svg' => '', 'custom_lock_screen' => 'no', 'can_request_join' => 'no', 'layout_style' => 'timeline', 'show_sidebar' => 'yes', 'og_image' => '', 'links' => [], 'topic_required' => 'no', 'hide_members_count' => 'no', // yes / no 'members_page_status' => 'members_only', // members_only, everybody, logged_in, admin_only ]; } public function setSettingsAttribute($value) { $this->attributes['settings'] = maybe_serialize($value); } public function getPublicPermissions() { if ($this->privacy == 'public') { $hasDocuments = defined('FLUENT_COMMUNITY_PRO') && Arr::get($this->settings, 'document_library') == 'yes'; $hasMediaGallery = defined('FLUENT_COMMUNITY_PRO') && Arr::get($this->settings, 'media_gallery') == 'yes'; return [ 'can_view_info' => true, 'can_view_posts' => true, 'can_view_members' => $this->canViewMembers(null), 'can_create_post' => false, 'can_view_documents' => $hasDocuments && Arr::get($this->settings, 'document_access') == 'everybody', 'can_view_media' => $hasMediaGallery && Arr::get($this->settings, 'media_access') == 'everybody', ]; } return [ 'can_view_info' => false, 'can_view_posts' => false, ]; } public function getUserPermissions($user = null) { if (!$user) { return $this->getPublicPermissions(); } return $user->getSpacePermissions($this); } public function canViewMembers($user) { $viewStatus = Arr::get($this->settings, 'members_page_status'); if ($viewStatus === 'everybody') { return true; } if (!$user) { return false; } if ($viewStatus === 'logged_in') { return true; } if ($viewStatus === 'members_only') { $membership = $this->getMembership($user->ID); return $membership && isset($membership->pivot) && $membership->pivot->status === 'active'; } return $this->isAdmin($user->ID, true); } public function verifyUserPermisson($user, $permission, $exception = true) { $permissions = $this->getUserPermissions($user); $hasPermission = $permissions[$permission] ?? false; if (!$hasPermission && $exception) { /* translators: %s is the permission name */ throw new \Exception(esc_html(sprintf(__('Sorry you do not have permission %s', 'fluent-community'), $permission)), 403); } return $hasPermission; } public function setLockscreen($settingFields) { return $this->updateCustomMeta('lockscreen_settings', $settingFields); } public function getPermalink() { if ($this->type == 'community') { return Helper::baseUrl('space/' . $this->slug . '/home'); } if ($this->type == 'course') { return Helper::baseUrl('course/' . $this->slug . '/lessons'); } if ($this->type == 'sidebar_link') { $permalink = Arr::get($this->settings, 'permalink'); if ($permalink) { return $permalink; } } return Helper::baseUrl('/'); } public function getLockscreen() { return LockscreenService::getLockscreenSettings($this); } public function hasPaywallIntegration() { return !empty(Arr::get($this->settings, 'cart_product_ids', [])); } public function getCustomMeta($key, $default = null) { return Helper::getSpaceMeta($this->id, $key, $default); } public function updateCustomMeta($key, $value) { return Helper::updateSpaceMeta($this->id, $key, $value); } public function getIconMark($isHtml = true) { if (!$isHtml) { return ''; } if ($this->logo) { return ''; } if ($imoji = Arr::get($this->settings, 'emoji')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure return '' . $imoji . ''; } if ($svg = Arr::get($this->settings, 'shape_svg')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure return '' . $svg . ''; } return ''; } public function syncTopics($topicIds) { $topics = Term::where('taxonomy_name', 'post_topic')->whereIn('id', $topicIds)->get(); $topicIds = $topics->pluck('id')->toArray(); $existIds = []; foreach ($topicIds as $topicId) { $relation = Meta::where('object_id', $topicId) ->where('object_type', 'term_space_relation') ->where('meta_key', $this->id) ->first(); if ($relation) { $existIds[] = $relation->id; continue; } // We have to create one $new = Meta::create([ 'object_id' => $topicId, 'meta_key' => $this->id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'object_type' => 'term_space_relation', ]); $existIds[] = $new->id; } // Remove other relations Meta::whereNotIn('id', $existIds) ->where('object_type', 'term_space_relation') ->where('meta_key', $this->id) ->delete(); Utility::forgetCache('fluent_community_post_topics'); return $this; } protected static function generateNewSlug($newModel) { if ($newModel->title) { // Remove the emojis $title = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $newModel->title); // get the first 30 char from the title $title = mb_substr($title, 0, 30, 'UTF-8'); } else { $title = static::$type . '-' . time(); } $title = Helper::normalizeToAscii($title); $title = remove_accents($title); $title = strtolower($title); $title = trim(preg_replace('/[^a-z0-9-_]/', ' ', $title)); $slugNum = self::withoutGlobalScopes()->where('type', static::$type)->count(); $title = sanitize_title($title, static::$type . '-' . $slugNum); // check if the slug is already exists $slug = $title; $count = 1; while (self::withoutGlobalScopes()->where('slug', $slug)->exists()) { if ($count == 5) { $count = time(); } $slug = $title . '-' . (++$slugNum); ++$count; } return $slug; } public function formatSpaceData($user) { $userId = $user ? $user->ID : null; $this->permissions = $this->getUserPermissions($user); $this->description_rendered = wpautop($this->description); $this->membership = $this->getMembership($userId); $this->topics = Utility::getTopicsBySpaceId($this->id); $headerLinks = [ [ 'title' => __('Posts', 'fluent-community'), 'route' => [ 'name' => 'space_feeds', ], ], ]; if (Arr::get($this->permissions, 'can_view_members')) { $headerLinks[] = [ 'title' => __('Members', 'fluent-community'), 'route' => [ 'name' => 'space_members', ], ]; } $this->header_links = apply_filters('fluent_community/space_header_links', $headerLinks, $this); if ($this->isAdmin($userId, true)) { return $this; } $this->lockscreen_config = LockscreenService::getLockscreenConfig($this, $this->membership, true); $spaceSettings = $this->settings; $spaceLinks = Arr::get($spaceSettings, 'links', []); $spaceSettings['links'] = array_values(array_filter($spaceLinks, function ($item) use ($user) { return Helper::isLinkAccessible($item, $user); })); $this->settings = $spaceSettings; return $this; } }