get('space'); $userId = $request->getSafe('user_id', 'intval', ''); $selectedTopic = $request->getSafe('topic_slug', 'sanitize_text_field', ''); $search = $request->get('search'); if ($bySpace) { // just for validation $space = BaseSpace::where('slug', $bySpace)->first(); if (!$space) { return $this->sendError('Invalid space slug'); } } $feedsQuery = Feed::where('status', 'published') ->select(Feed::$publicColumns) ->with([ 'xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'comments.xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'space', 'reactions' => function ($q) { $q->with([ 'xprofile' => function ($query) { $query->select(['user_id', 'avatar']); } ]) ->where('type', 'like') ->limit(3); }, 'terms' => function ($q) { $q->select(['title', 'slug']) ->where('taxonomy_name', 'post_topic'); } ] ) ->searchBy($search) ->byTopicSlug($selectedTopic) ->customOrderBy($request->get('type', '')); $stickyFeed = null; $disableSticky = $request->get('disable_sticky', '') == 'yes' || !!$search || !!$selectedTopic; if($bySpace) { $feedsQuery = $feedsQuery->filterBySpaceSlug($bySpace); } if ($bySpace && !$disableSticky) { $feedsQuery = $feedsQuery->where('is_sticky', 0); if ($request->page == 1) { $stickyFeed = Feed::where('space_id', $space->id) ->where('is_sticky', 1) ->with([ 'xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'comments.xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'space' ] ) ->first(); } } if ($userId) { $feedsQuery = $feedsQuery->where('user_id', $userId); if ($userId != get_current_user_id()) { $feedsQuery = $feedsQuery->byUserAccess(get_current_user_id()); } } else { $feedsQuery->byUserAccess(get_current_user_id()); } do_action_ref_array('fluent_community/feeds_query', [&$feedsQuery, $request->all()]); $feeds = $feedsQuery->paginate(); // add $stickyFeed to the first page if ($stickyFeed) { $stickyFeed = $this->transformFeed($stickyFeed); } $feeds->getCollection()->each(function ($feed) { $this->transformFeed($feed); }); $data = [ 'feeds' => $feeds, 'sticky' => $stickyFeed ]; $isMainFeed = $request->get('page') == 1 && !$search && !$userId; if ($isMainFeed && get_current_user_id()) { $data['last_fetched_timestamp'] = current_time('timestamp'); } return $data; } public function getFeedBySlug(Request $request, $feed_slug) { if ($request->get('context') == 'edit') { $feed = Feed::where('slug', $feed_slug)->first(); if (!$feed || !$feed->hasEditAccess(get_current_user_id())) { return $this->sendError([ 'message' => 'You do not have permission to edit this feed' ]); } return [ 'feed' => FeedsHelper::transformForEdit($feed) ]; } $feed = Feed::where('slug', $feed_slug) ->select(Feed::$publicColumns) ->with([ 'xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'space', 'comments.xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'reactions' => function ($q) { $q->with([ 'xprofile' => function ($query) { $query->select(['user_id', 'avatar']); } ]) ->where('type', 'like') ->limit(3); }, 'terms' => function ($q) { $q->select(['title', 'slug']) ->where('taxonomy_name', 'post_topic'); } ]) ->byUserAccess($this->getUserId()) ->first(); if (!$feed) { return $this->sendError([ 'message' => __('The feed could not be found', 'fluent-commuity') ], 404); } $this->transformFeed($feed); return [ 'feed' => $feed ]; } public function getBookmarks(Request $request) { $userId = get_current_user_id(); $feedsQuery = Feed::where('status', 'published') ->select(Feed::$publicColumns) ->with([ 'xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'comments.xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'space' ] ) ->byBookMarked($userId) ->byUserAccess($userId) ->searchBy($request->get('search')); if ($type = $request->get('type')) { $feedsQuery = $feedsQuery->where('type', $type); } $feeds = $feedsQuery->orderBy('id', 'DESC') ->paginate(); $feeds->getCollection()->each(function ($feed) { $this->transformFeed($feed); }); $data = [ 'feeds' => $feeds ]; if ($request->get('page') == 1) { $lastItem = FeedsHelper::getLastFeedId(); if ($lastItem) { $data['last_id'] = $lastItem; } } return $data; } public function store(Request $request) { $user = $this->getUser(true); do_action('fluent_community/check_rate_limit/create_post', $user); $requestData = $request->all(); $data = $this->sanitizeAndValidateData($requestData); if ($isDulicate = $this->checkForDuplicatePost($user->ID, $data['message'])) { return $isDulicate; } $feed = new Feed(); $feed->user_id = $user->ID; if ($spaceSlug = $request->get('space')) { $data['space_id'] = $this->validateAndSetSpace($spaceSlug, $user); } else if (!Helper::hasGlobalPost()) { return $this->sendError([ 'message' => __('Please select a valid space to post in.', 'fluent-community') ]); } $message = $data['message']; $mentions = FeedsHelper::getMentions($data['message'], Arr::get($data, 'space_id')); if ($mentions) { $data['message'] = $message; $message = $mentions['text']; } // replace new line with br $data['message_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($message)); [$data, $mediaItems] = FeedsHelper::processFeedMetaData($data, $requestData); $data = apply_filters('fluent_community/feed/new_feed_data', $data, $requestData); $feed->fill($data); $feed->save(); if ($mediaItems) { $this->saveMediaItems($feed, $mediaItems); } $this->handleMentions($feed, $mentions ?? []); $feed->load(['xprofile', 'comments.xprofile']); if ($feed->space_id) { $feed->load(['space']); $topicIds = (array)$request->get('topic_ids', []); // take only max topics per post if ($topicIds) { $topicsConfig = Helper::getTopicsConfig(); $topicIds = array_slice($topicIds, 0, $topicsConfig['max_topics_per_post']); $feed->attachTopics($topicIds, false); } } do_action('fluent_community/feed/created', $feed); if ($feed->space_id) { do_action('fluent_community/space_feed/created', $feed); } return [ 'feed' => $this->transformFeed($feed), 'message' => __('Your post has been published', 'fluent-community'), 'last_fetched_timestamp' => current_time('timestamp') ]; } public function update(Request $request, $feedId) { $requestData = $request->all(); $data = $this->sanitizeAndValidateData($requestData); $user = $this->getUser(true); $existingFeed = Feed::findOrFail($feedId); $user->canEditFeed($existingFeed, true); $message = $data['message']; $mentions = FeedsHelper::getMentions($data['message'], Arr::get($data, 'space_id')); if ($mentions) { $data['message'] = $message; $message = $mentions['text']; } // replace new line with br $data['message_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($message)); [$data, $mediaItems] = FeedsHelper::processFeedMetaData($data, $requestData, $existingFeed); $data = apply_filters('fluent_community/feed/update_feed_data', $data, $requestData); if ($message != $existingFeed->message) { $data['meta']['last_edited'] = [ 'user_id' => $user->ID, 'time' => current_time('mysql') ]; } $data = apply_filters('fluent_community/feed/update_data', $data, $existingFeed); $existingFeed->fill($data); $dirty = $existingFeed->getDirty(); $existingFeed->fill($data); $existingFeed->save(); if ($message != $existingFeed->message) { $editHistory = $existingFeed->getCustomMeta('_edit_history', []); if (!$editHistory) { $editHistory = []; } $editHistory[] = array_filter([ 'user_id' => $user->ID, 'time' => current_time('mysql'), 'prev_message' => $existingFeed->message, 'prev_title' => $existingFeed->title ]); // get last 5 edit history $editHistory = array_slice($editHistory, -5); $existingFeed->updateCustomMeta('_edit_history', $editHistory); } if ($mediaItems) { $this->saveMediaItems($existingFeed, $mediaItems); } $existingFeed->load(['xprofile', 'comments.xprofile']); if ($existingFeed->space_id) { $existingFeed->load(['space']); $topicIds = (array)Arr::get($requestData, 'topic_ids', []); // take only max topics per post if ($topicIds) { $topicsConfig = Helper::getTopicsConfig(); $topicIds = array_slice($topicIds, 0, $topicsConfig['max_topics_per_post']); $existingFeed->attachTopics($topicIds, true); } } if ($dirty) { do_action('fluent_community/feed/updated', $existingFeed, $dirty); if ($existingFeed->space_id) { do_action('fluent_community/space_feed/updated', $existingFeed); } } return [ 'feed' => $this->transformFeed($existingFeed), 'message' => __('Your post has been updated', 'fluent-community') ]; } public function patchFeed(Request $request, $feedId) { $feed = Feed::findOrFail($feedId); $user = $this->getUser(true); $isMod = $user->isCommunityModerator(); $isAuthor = $feed->user_id == $user->ID; if (!$isMod && !$isAuthor) { return $this->sendError([ 'message' => __('You do not have permission to perform this action', 'fluent-community') ]); } $allData = $request->all(); $validKeys = ['is_sticky', 'priority', 'comments_disabled']; if (!$isMod) { $validKeys = ['comments_disabled']; } $data = Arr::only($allData, $validKeys); $data = array_map('intval', $data); if (isset($data['is_sticky'])) { $data['is_sticky'] = $data['is_sticky'] ? 1 : 0; if ($data['is_sticky'] && $feed->space_id) { // remove all the sticky posts from the space Feed::where('space_id', $feed->space_id)->update(['is_sticky' => 0]); } } if (isset($data['comments_disabled'])) { $meta = $feed->meta; $meta['comments_disabled'] = $data['comments_disabled'] ? 'yes' : 'no'; $data['meta'] = $meta; } if ($data) { $feed->fill($data); $dirty = $feed->getDirty(); if ($dirty) { $feed->save(); do_action('fluent_community/feed/updated', $feed, $dirty); } } return [ 'feed' => $feed, 'message' => __('Feed updated', 'fluent-community') ]; } public function getLinks(Request $request) { return [ 'links' => Helper::getFeedLinks() ]; } public function updateLinks(Request $request) { $links = $request->get('links', []); $links = array_map(function ($link) { return CustomSanitizer::santizeLinkItem($link); }, $links); Helper::updateFeedLinks($links); return [ 'message' => __('Links have been updated.', 'fluent-community'), 'links' => $links ]; } private function saveMediaItems($feed, $mediaItems) { foreach ($mediaItems as $media) { $media->feed_id = $feed->id; $media->is_active = 1; $media->object_source = 'feed'; $media->save(); } } private function handleMentions($feed, $mentions) { if ($mentions) { do_action('fluent_community/feed_mentioned', $feed, $mentions['users']); } } private function sanitizeAndValidateData($data) { $data['type'] = 'text'; $this->validate($data, [ 'message' => 'required' ], [ 'message.required' => __('Message is required', 'fluent-community'), ]); return FeedsHelper::sanitizeAndValidateData($data); } private function checkForDuplicatePost($userId, $message) { $message = trim($message); $exist = Feed::where('user_id', $userId) ->where('message', $message) ->where('created_at', '>', gmdate('Y-m-d H:i:s', current_time('timestamp') - 7 * 24 * 60 * 60)) ->first(); if ($exist) { return $this->sendError(['message' => 'No duplicate post please!']); } return false; } private function validateAndSetSpace($spaceSlug, $user) { if ($spaceSlug == '__self__post__') { if (!Helper::hasGlobalPost()) { throw new \Exception(__('Please select a valid space to post in', 'fluent-community')); } return null; } $space = Space::where('slug', $spaceSlug)->first(); if (!$space) { throw new \Exception(__('Please select a valid space to post in', 'fluent-community')); } $user->verifySpacePermission('can_create_post', $space); return $space->id; } public function deleteFeed(Request $request, $feed_id) { $feed = Feed::findOrFail($feed_id); $user = User::find(get_current_user_id()); $user->canDeleteFeed($feed, true); do_action('fluent_community/feed/before_deleted', $feed); $feed->delete(); do_action('fluent_community/feed/deleted', $feed_id); return [ 'message' => 'Feed has been deleted successfully' ]; } public function deleteMediaPreview(Request $request, $feed_id) { $feed = Feed::findOrFail($feed_id); $user = User::find(get_current_user_id()); $user->canDeleteFeed($feed, true); do_action('fluent_community/feed/media_deleted', $feed->media); $meta = $feed->meta; $meta['media_preview'] = null; $feed->meta = $meta; $feed->save(); return [ 'message' => __('Media preview image has been removed successfully.', 'fluent-community') ]; } public function handleMediaUpload(Request $request) { $allowedTypes = implode( ',', apply_filters('fluent_community/support_attachment_types', [ 'image/jpeg', 'image/pjpeg', 'image/jpeg', 'image/pjpeg', 'image/png', 'image/gif', 'image/webp' ]) ); $files = $this->validate($this->request->files(), [ 'file' => 'mimetypes:' . $allowedTypes, ], [ 'file.mimetypes' => __('The file must be an image type.', 'fluent-community') ]); $uploadedFiles = FileSystem::put($files); $file = $uploadedFiles[0]; $willWebPConvert = apply_filters('fluent_community/convert_image_to_webp', true, $file); if ($request->get('resize') && $maxWidth = $request->get('max_width')) { $upload_dir = wp_upload_dir(); $fileUrl = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file['url']); $editor = wp_get_image_editor($fileUrl); if (!is_wp_error($editor) && $editor->get_size()['width'] > $maxWidth) { // Current file extension $ext = pathinfo($file['url'], PATHINFO_EXTENSION); $imageExtensions = ['jpg', 'jpeg', 'png', 'gif']; $willConvert = in_array($ext, $imageExtensions) && $willWebPConvert; if ($willConvert) { $imageExtensions = array_map(function ($ext) { return '.' . $ext; }, $imageExtensions); $fileUrl = str_replace($imageExtensions, '.webp', $fileUrl); $file['file'] = str_replace($imageExtensions, '.webp', $file['file']); $file['url'] = str_replace($imageExtensions, '.webp', $file['url']); $file['type'] = 'image/webp'; } // resize the image $editor->resize($maxWidth, null, false); $editor->set_quality(90); if ($willConvert) { $editor->save($fileUrl, 'image/webp'); // remove original file now wp_delete_file(str_replace('.webp', '.' . $ext, $fileUrl)); $file['is_converted'] = true; } else { $editor->save($fileUrl); } $file['meta'] = [ 'width' => $editor->get_size()['width'], 'height' => $editor->get_size()['height'] ]; } $file['path'] = $upload_dir['basedir'] . '/fluent-community/' . $file['file']; } else { $upload_dir = wp_upload_dir(); $file['path'] = $upload_dir['basedir'] . '/fluent-community/' . $file['file']; } if ($willWebPConvert && empty($file['is_converted']) && !$request->get('skip_convert')) { $path = $file['path']; $extension = pathinfo($path, PATHINFO_EXTENSION); $convertFromExtensions = ['png', 'jpg', 'jpeg', 'gif']; if ($extension != 'webp' && in_array($extension, $convertFromExtensions)) { // Let's convert to webp $editor = wp_get_image_editor($file['path']); if (!is_wp_error($editor)) { $orginalPath = $file['path']; $file['path'] = str_replace('.' . $extension, '.webp', $file['path']); $file['url'] = str_replace('.' . $extension, '.webp', $file['url']); $file['type'] = 'image/webp'; $editor->save($file['path'], 'image/webp'); wp_delete_file($orginalPath); $file['meta'] = [ 'width' => $editor->get_size()['width'], 'height' => $editor->get_size()['height'] ]; } } } $mediaData = [ 'media_type' => $file['type'], 'driver' => 'local', 'media_path' => $file['path'], 'media_url' => $file['url'], 'settings' => Arr::get($file, 'meta', []) ]; $mediaData = apply_filters('fluent_community/media_upload_data', $mediaData, $file); if (is_wp_error($mediaData)) { return $this->sendError([ 'message' => $mediaData->get_error_message(), 'errors' => $mediaData->get_error_data() ]); } if (!$mediaData) { return $this->sendError([ 'message' => 'Error while uploading the media' ]); } // Let's create the media now $media = Media::create($mediaData); $mediaUrl = $media->public_url; $mediaUrl = add_query_arg([ 'media_key' => $media->media_key, ], $mediaUrl); return [ 'media' => [ 'url' => $mediaUrl, 'media_key' => $media->media_key, 'type' => $media->media_type, 'width' => Arr::get($media->settings, 'width'), 'height' => Arr::get($media->settings, 'height') ] ]; } public function getTicker(Request $request) { do_action('fluent_communit/track_activity'); $lastLoadedTimeStamp = $request->get('last_fetched_timestamp'); //check if $lastLoadedTimeStamp is valid date if (!$lastLoadedTimeStamp || (current_time('timestamp') - $lastLoadedTimeStamp) > HOUR_IN_SECONDS) { return [ 'last_fetched_timestamp' => current_time('timestamp'), 'error' => 'Invalid timestamp', 'given' => $lastLoadedTimeStamp ]; } $userId = get_current_user_id(); if (!$userId) { return [ 'last_fetched_timestamp' => current_time('timestamp'), 'error' => 'Invalid user' ]; } $newItemsCount = Feed::where('created_at', '>', date('Y-m-d H:i:s', $lastLoadedTimeStamp)) ->where('status', 'published') ->byUserAccess(get_current_user_id()) ->count(); $notificationCount = NotificationSubscriber::unread()->where('user_id', $userId)->count(); return apply_filters('fluent_community/feed_ticker', [ 'last_fetched_timestamp' => current_time('timestamp'), 'new_items_count' => $newItemsCount > 10 ? 10 : $newItemsCount, 'unread_notification_count' => $notificationCount ]); } public function getOembed(Request $request) { $url = $request->get('url'); // check if the url is valid $metaData = RemoteUrlParser::parse($url); if ($metaData && !is_wp_error($metaData)) { return [ 'oembed' => $metaData ]; } return $this->send([ 'message' => 'No oembed data found', 'url' => $url ]); } public function markdownToHtml(Request $request) { $message = trim(sanitize_textarea_field($request->get('text', ''))); $html = FeedsHelper::mdToHtml($message); return [ 'html' => $html ]; } private function transformFeed(Feed $feed) { $userId = $this->getUserId(); if ($userId) { $feed->has_user_react = $feed->hasUserReact($userId, 'like'); $feed->bookmarked = $feed->hasUserReact($userId, 'bookmark'); $likedIds = FeedsHelper::getLikedIdsByUserFeedId($feed->id, get_current_user_id()); $feed->comments->each(function ($comment) use ($likedIds) { if ($likedIds && in_array($comment->id, $likedIds)) { $comment->liked = 1; } }); if ($feed->content_type == 'survey') { $votedOptions = $feed->getSurveyCastsByUserId($userId); if ($votedOptions) { $surveyConfig = Arr::get($feed->meta, 'survey_config', []); foreach ($surveyConfig['options'] as $index => $option) { if (in_array($option['slug'], $votedOptions)) { $surveyConfig['options'][$index]['voted'] = true; } } $meta = $feed->meta; $meta['survey_config'] = $surveyConfig; $feed->meta = $meta; } } } return $feed; } }