PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 1.0.93
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v1.0.93
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 / Http / Controllers / FeedsController.php

FeedsController.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 1.0.93, at app/Http/Controllers/FeedsController.php

1,200 lines 39.0 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\Http\Controllers;
4
5 use FluentCommunity\App\Functions\Utility;
6 use FluentCommunity\App\Models\Media;
7 use FluentCommunity\App\Models\NotificationSubscriber;
8 use FluentCommunity\App\Models\Space;
9 use FluentCommunity\App\Models\User;
10 use FluentCommunity\App\Services\CustomSanitizer;
11 use FluentCommunity\App\Services\FeedsHelper;
12 use FluentCommunity\App\Services\Helper;
13 use FluentCommunity\App\Services\Libs\FileSystem;
14 use FluentCommunity\App\Services\ProfileHelper;
15 use FluentCommunity\App\Services\RemoteUrlParser;
16 use FluentCommunity\Framework\Http\Request\Request;
17 use FluentCommunity\App\Models\Comment;
18 use FluentCommunity\App\Models\Feed;
19 use FluentCommunity\App\Models\Reaction;
20 use FluentCommunity\App\Models\BaseSpace;
21 use FluentCommunity\Framework\Support\Arr;
22
23 class FeedsController extends Controller
24 {
25 public function get(Request $request)
26 {
27 $bySpace = $request->get('space');
28 $userId = $request->getSafe('user_id', 'intval', '');
29 $selectedTopic = $request->getSafe('topic_slug', 'sanitize_text_field', '');
30
31 $search = $request->get('search');
32
33 if ($bySpace) {
34 // just for validation
35 $space = BaseSpace::where('slug', $bySpace)->first();
36 if (!$space) {
37 return $this->sendError('Invalid space slug');
38 }
39 }
40
41 $feedsQuery = Feed::where('status', 'published')
42 ->select(Feed::$publicColumns)
43 ->with([
44 'xprofile' => function ($q) {
45 $q->select(ProfileHelper::getXProfilePublicFields());
46 },
47 'comments.xprofile' => function ($q) {
48 $q->select(ProfileHelper::getXProfilePublicFields());
49 },
50 'space',
51 'reactions' => function ($q) {
52 $q->with([
53 'xprofile' => function ($query) {
54 $query->select(['user_id', 'avatar']);
55 }
56 ])
57 ->where('type', 'like')
58 ->limit(3);
59 }
60 ]
61 )
62 ->searchBy($search)
63 ->byTopicSlug($selectedTopic)
64 ->customOrderBy($request->get('type', ''));
65
66 $stickyFeed = null;
67
68 $disableSticky = $request->get('disable_sticky', '') == 'yes' || !!$search || !!$selectedTopic;
69
70 if ($bySpace && !$disableSticky) {
71 $feedsQuery = $feedsQuery->filterBySpaceSlug($bySpace)
72 ->where('is_sticky', 0);
73
74 if ($request->page == 1) {
75 $stickyFeed = Feed::where('space_id', $space->id)
76 ->where('is_sticky', 1)
77 ->with([
78 'xprofile' => function ($q) {
79 $q->select(ProfileHelper::getXProfilePublicFields());
80 },
81 'comments.xprofile' => function ($q) {
82 $q->select(ProfileHelper::getXProfilePublicFields());
83 },
84 'space'
85 ]
86 )
87 ->first();
88 }
89 }
90
91 if ($userId) {
92 $feedsQuery = $feedsQuery->where('user_id', $userId);
93 if ($userId != get_current_user_id()) {
94 $feedsQuery = $feedsQuery->byUserAccess(get_current_user_id());
95 }
96 } else {
97 $feedsQuery->byUserAccess(get_current_user_id());
98 }
99
100 do_action_ref_array('fluent_community/feeds_query', [&$feedsQuery, $request->all()]);
101
102 $feeds = $feedsQuery->paginate();
103
104 // add $stickyFeed to the first page
105 if ($stickyFeed) {
106 $stickyFeed = $this->transformFeed($stickyFeed);
107 }
108
109 $feeds->getCollection()->each(function ($feed) {
110 $this->transformFeed($feed);
111 });
112
113 $data = [
114 'feeds' => $feeds,
115 'sticky' => $stickyFeed
116 ];
117
118 if ($request->get('page') == 1) {
119 $lastItem = FeedsHelper::getLastFeedId();
120 if ($lastItem) {
121 $data['last_id'] = $lastItem;
122 }
123 }
124
125 return $data;
126 }
127
128 public function getFeedBySlug(Request $request, $feed_slug)
129 {
130 if ($request->get('context') == 'edit') {
131 $feed = Feed::where('slug', $feed_slug)->with(['space'])->first();
132
133 if (!$feed || !$feed->hasEditAccess(get_current_user_id())) {
134 return $this->sendError([
135 'message' => 'You do not have permission to edit this feed'
136 ]);
137 }
138
139 return [
140 'feed' => $feed
141 ];
142 }
143
144 $feed = Feed::where('slug', $feed_slug)
145 ->select(Feed::$publicColumns)
146 ->with([
147 'xprofile' => function ($q) {
148 $q->select(ProfileHelper::getXProfilePublicFields());
149 },
150 'space',
151 'comments.xprofile' => function ($q) {
152 $q->select(ProfileHelper::getXProfilePublicFields());
153 },
154 'reactions' => function ($q) {
155 $q->with([
156 'xprofile' => function ($query) {
157 $query->select(['user_id', 'avatar']);
158 }
159 ])
160 ->where('type', 'like')
161 ->limit(3);
162 }
163 ])
164 ->byUserAccess($this->getUserId())
165 ->first();
166
167 if (!$feed) {
168 return $this->sendError([
169 'message' => __('The feed could not be found', 'fluent-commuity')
170 ], 404);
171 }
172
173 $this->transformFeed($feed);
174
175 return [
176 'feed' => $feed
177 ];
178 }
179
180 public function getBookmarks(Request $request)
181 {
182 $userId = get_current_user_id();
183
184 $feedsQuery = Feed::where('status', 'published')
185 ->select(Feed::$publicColumns)
186 ->with([
187 'xprofile' => function ($q) {
188 $q->select(ProfileHelper::getXProfilePublicFields());
189 },
190 'comments.xprofile' => function ($q) {
191 $q->select(ProfileHelper::getXProfilePublicFields());
192 },
193 'space'
194 ]
195 )
196 ->byBookMarked($userId)
197 ->byUserAccess($userId)
198 ->searchBy($request->get('search'));
199
200
201 if ($type = $request->get('type')) {
202 $feedsQuery = $feedsQuery->where('type', $type);
203 }
204
205 $feeds = $feedsQuery->orderBy('id', 'DESC')
206 ->paginate();
207
208 $feeds->getCollection()->each(function ($feed) {
209 $this->transformFeed($feed);
210 });
211
212 $data = [
213 'feeds' => $feeds
214 ];
215
216 if ($request->get('page') == 1) {
217 $lastItem = FeedsHelper::getLastFeedId();
218 if ($lastItem) {
219 $data['last_id'] = $lastItem;
220 }
221 }
222
223 return $data;
224 }
225
226 public function store(Request $request)
227 {
228 $userId = get_current_user_id();
229 $user = $this->getUser(true);
230
231 do_action('fluent_community/check_rate_limit/create_post', $user);
232
233 $requestData = $request->all();
234
235 $data = $this->sanitizeAndValidateData($requestData);
236
237 if ($isDulicate = $this->checkForDuplicatePost($userId, $data['message'])) {
238 return $isDulicate;
239 }
240
241 $feed = new Feed();
242 $feed->user_id = $userId;
243
244 if ($spaceSlug = $request->get('space')) {
245 $data['space_id'] = $this->validateAndSetSpace($spaceSlug, $user);
246 } else {
247 // Check if the user has global post permission
248 if (!Helper::hasGlobalPost()) {
249 return $this->sendError([
250 'message' => __('Please select a valid space to post in', 'fluent-community')
251 ]);
252 }
253 }
254
255 $message = $data['message'];
256
257 $mentions = FeedsHelper::getMentions($data['message'], Arr::get($data, 'space_id'));
258
259 if ($mentions) {
260 $data['message'] = $message;
261 $message = $mentions['text'];
262 }
263
264 // replace new line with br
265 $data['message_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($message));
266
267 $mediaItems = null;
268
269 if (!empty($data['survey'])) {
270 $this->handleSurveyConfig($data);
271 } elseif (Arr::get($requestData, 'meta.media_preview.provider') == 'giphy') {
272 $this->setGiphyMediaPreview($data, $requestData);
273 } else {
274 $mediaItems = $this->processNewMedia($requestData, $data);
275 }
276
277 $data = apply_filters('fluent_community/feed_data/new', $data);
278
279 $feed->fill($data);
280
281 $feed->save();
282
283 if ($mediaItems) {
284 $this->saveMediaItems($feed, $mediaItems);
285 }
286
287 $this->handleMentions($feed, $mentions ?? []);
288 $this->syncHashTags($feed, $data['message']);
289
290 $feed->load(['xprofile', 'comments.xprofile']);
291
292 if ($feed->space_id) {
293 $feed->load(['space']);
294 $topicIds = $request->get('topic_ids', []);
295 // take only first 5 topics
296 if ($topicIds) {
297 $topicIds = array_slice($topicIds, 0, apply_filters('fluent_community/max_topic_per_post', 5));
298 $feed->attachTopics($topicIds, false);
299 }
300 }
301
302 do_action('fluent_community/feed/created', $feed);
303
304 if ($feed->space_id) {
305 do_action('fluent_community/space_feed/created', $feed);
306 }
307
308 return [
309 'feed' => $feed,
310 'message' => __('Feed added', 'fluent-community')
311 ];
312 }
313
314 public function update(Request $request, $feedId)
315 {
316 $requestData = $request->all();
317 $data = $this->sanitizeAndValidateData($requestData);
318
319 $userId = get_current_user_id();
320 $user = User::findOrFail($userId);
321
322 $feed = Feed::find($feedId);
323
324 if (!$feed) {
325 return $this->sendError(['message' => __('Feed not found', 'fluent-community')]);
326 }
327
328 $user->canEditFeed($feed, false);
329
330 if (!$feed->hasEditAccess($userId)) {
331 return $this->send('You do not have permission to edit this feed', 403);
332 }
333
334 if ($spaceSlug = $request->get('space')) {
335 $data['space_id'] = $this->validateAndSetSpace($spaceSlug, $user);
336 }
337
338 $message = $data['message'];
339
340 $mentions = FeedsHelper::getMentions($data['message'], Arr::get($data, 'space_id'));
341
342 if ($mentions) {
343 $data['message'] = $message;
344 $message = $mentions['text'];
345 }
346
347 // replace new line with br
348 $data['message_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($message));
349 $mediaItems = null;
350
351 if (!empty($data['survey'])) {
352 $this->handleSurveyConfig($data);
353 } elseif (Arr::get($requestData, 'meta.media_preview.provider') == 'giphy') {
354 $this->setGiphyMediaPreview($data, $requestData);
355 } else {
356 $mediaItems = $this->processExistingMedia($feed, $requestData, $data);
357 }
358
359 if ($message != $feed->message) {
360 $meta = $feed->meta;
361 $meta['last_edited'] = [
362 'user_id' => $userId,
363 'time' => current_time('mysql')
364 ];
365
366 $editHistory = $feed->getCustomMeta('_edit_history', []);
367
368 if (!$editHistory) {
369 $editHistory = [];
370 }
371
372 $editHistory[] = array_filter([
373 'user_id' => $userId,
374 'time' => current_time('mysql'),
375 'prev_message' => $feed->message,
376 'prev_title' => $feed->title
377 ]);
378
379 // get last 5 edit history
380 $editHistory = array_slice($editHistory, -5);
381 $feed->updateCustomMeta('_edit_history', $editHistory);
382 $data['meta'] = $meta;
383 }
384
385 $data = apply_filters('fluent_community/feed_data/update', $data, $feed);
386 $feed->fill($data);
387 $feed->save();
388
389 if ($mediaItems) {
390 $this->saveMediaItems($feed, $mediaItems);
391 }
392
393 $this->syncHashTags($feed, $data['message']);
394
395 $feed->load(['xprofile', 'comments.xprofile']);
396
397 if ($feed->space_id) {
398 $feed->load(['space']);
399 }
400
401 return [
402 'feed' => $feed,
403 'message' => __('Feed updated', 'fluent-community')
404 ];
405 }
406
407 public function patchFeed(Request $request, $feedId)
408 {
409 $feed = Feed::findOrFail($feedId);
410 $user = $this->getUser(true);
411
412 $isMod = $user->isCommunityModerator();
413 $isAuthor = $feed->user_id == $user->ID;
414
415 if (!$isMod && !$isAuthor) {
416 return $this->sendError([
417 'message' => __('You do not have permission to perform this action', 'fluent-community')
418 ]);
419 }
420
421 $allData = $request->all();
422 $validKeys = ['is_sticky', 'priority', 'comments_disabled'];
423
424 if (!$isMod) {
425 $validKeys = ['comments_disabled'];
426 }
427
428 $data = Arr::only($allData, $validKeys);
429
430 $data = array_map('intval', $data);
431
432 if (isset($data['is_sticky'])) {
433 $data['is_sticky'] = $data['is_sticky'] ? 1 : 0;
434 if ($data['is_sticky'] && $feed->space_id) {
435 // remove all the sticky posts from the space
436 Feed::where('space_id', $feed->space_id)->update(['is_sticky' => 0]);
437 }
438 }
439
440 if (isset($data['comments_disabled'])) {
441 $meta = $feed->meta;
442 $meta['comments_disabled'] = $data['comments_disabled'] ? 'yes' : 'no';
443 $data['meta'] = $meta;
444 }
445
446 if ($data) {
447 $feed->fill($data);
448 $feed->save();
449 }
450
451 return [
452 'feed' => $feed,
453 'message' => __('Feed updated', 'fluent-community')
454 ];
455 }
456
457 public function getLinks(Request $request)
458 {
459 return [
460 'links' => Helper::getFeedLinks()
461 ];
462 }
463
464 public function updateLinks(Request $request)
465 {
466 $links = $request->get('links', []);
467
468 $links = array_map(function ($link) {
469 return CustomSanitizer::santizeLinkItem($link);
470 }, $links);
471
472 Helper::updateFeedLinks($links);
473
474 return [
475 'message' => __('Links has been updated', 'fluent-community'),
476 'links' => $links
477 ];
478 }
479
480 private function setGiphyMediaPreview(&$data, $requestData)
481 {
482 if (empty(Arr::get($requestData, 'meta.media_preview.image'))) {
483 return;
484 }
485
486 $data['meta']['media_preview'] = array_filter([
487 'image' => sanitize_url($requestData['meta']['media_preview']['image']),
488 'type' => Arr::get($requestData, 'meta.media_preview.type', 'image'),
489 'provider' => Arr::get($requestData, 'meta.media_preview.provider', ''),
490 'height' => Arr::get($requestData, 'meta.media_preview.height', 0),
491 'width' => Arr::get($requestData, 'meta.media_preview.width', 0),
492 ]);
493 }
494
495 private function handleSurveyConfig(&$data)
496 {
497 if (empty($data['meta'])) {
498 $data['meta'] = [];
499 }
500
501 $data['meta']['survey_config'] = $data['survey'];
502 $data['content_type'] = 'survey';
503 }
504
505 private function processNewMedia($requestData, &$data)
506 {
507 if ($mediaImages = Arr::get($requestData, 'media_images')) {
508 $uploadedImages = Helper::getMediaByProvider($mediaImages);
509 $uploadedMediaItems = Helper::getMediaItemsFromUrl($uploadedImages);
510 $mediaPreviews = $this->generateMediaPreviews($uploadedMediaItems);
511 $this->formatMediaMeta($mediaPreviews, $data, $mediaImages);
512 return $uploadedMediaItems;
513 }
514
515 if ($media = Arr::get($requestData, 'media')) {
516 $type = Arr::get($media, 'type', 'oembed');
517 if ($type == 'oembed') {
518 $url = Arr::get($media, 'url');
519 $metaData = RemoteUrlParser::parse($url);
520 if ($metaData && !is_wp_error($metaData) && (!empty($metaData['image']) || !empty($metaData['html']))) {
521 $data['meta']['media_preview'] = $metaData;
522 return [];
523 }
524 }
525 }
526
527 $urlMeta = $this->parseFirstUrl($data['message_rendered']);
528
529 if ($urlMeta) {
530 $data['meta'] = $urlMeta;
531 return [];
532 }
533
534 // Let's give option to the user to check if there is any fallback
535 do_action_ref_array('fluent_community/feed/meta_fallback', [&$data]);
536
537 return [];
538 }
539
540 private function processExistingMedia($feed, $requestData, &$data)
541 {
542 $images = (array)Arr::get($requestData, 'media_images', []);
543 $mediaImages = Helper::getMediaByProvider($images);
544 $metaMediaMetaItems = Helper::getMediaByProvider((array)Arr::get($requestData, 'meta.media_items', []));
545 $metaMediaPreview = array_filter((array)Arr::get($requestData, 'meta.media_preview', []));
546 $requestMediaIds = array_column($metaMediaMetaItems, 'media_id');
547
548 if (count($mediaImages) == 0 && count($metaMediaMetaItems) == 0) {
549 if (count($metaMediaPreview) === 0) {
550 do_action('fluent_community/feed/media_deleted', $feed->media);
551 $data['meta']['media_preview'] = null;
552 }
553
554 $previewMeta = $this->parseFirstUrl($data['message_rendered']);
555 if (count($metaMediaPreview) > 0) {
556 $data['meta']['media_preview'] = $metaMediaPreview;
557 } elseif (count($previewMeta) > 0) {
558 $data['meta'] = $previewMeta;
559 }
560
561 return [];
562 }
563
564 if (count($mediaImages) == 1 && (count($metaMediaMetaItems) == 0 || count($metaMediaPreview) > 0)) {
565 if (Arr::get($metaMediaPreview, 'is_uploaded')) {
566 $mediaImages[] = $metaMediaPreview['image'] . '?media_key=' . $feed->media[0]->media_key;
567 unset($data['meta']['media_preview']);
568 } elseif (count($metaMediaPreview) > 0) {
569 do_action('fluent_community/feed/media_deleted', $feed->media);
570 }
571
572 $mediaItems = Helper::getMediaItemsFromUrl($mediaImages);
573 $mediaPreviews = $this->generateMediaPreviews($mediaItems);
574
575 $this->formatMediaMeta($mediaPreviews, $data, $images);
576
577 return $mediaItems;
578 }
579
580 $message = $data['message'];
581
582 if ($mentions = FeedsHelper::getMentions($message, Arr::get($data, 'space_id'))) {
583 $message = $mentions['text'];
584 }
585
586 $data['message_rendered'] = FeedsHelper::mdToHtml($message);
587
588 if (count($mediaImages) > 1) {
589 $mediaItems = $this->processNewMedia($requestData, $data);
590 }
591
592 $deletedMediaItems = $feed->media()->whereNotIn('id', $requestMediaIds)->get();
593 do_action('fluent_community/feed/media_deleted', $deletedMediaItems);
594
595
596 if (!isset($data['meta']['media_items'])) {
597 $data['meta']['media_items'] = [];
598 }
599
600 if (!isset($data['meta']['media_preview'])) {
601 $data['meta']['media_preview'] = null;
602 }
603
604 if ($metaMediaMetaItems) {
605 $filteredData = array_filter($metaMediaMetaItems, function ($item) use ($requestMediaIds) {
606 return in_array($item['media_id'], $requestMediaIds);
607 });
608
609 if (count($mediaImages) == 1 && count($filteredData) > 0) {
610 $mediaItems = Helper::getMediaItemsFromUrl($mediaImages);
611 $newMediaItems = $this->generateMediaPreviews($mediaItems);
612 $filteredData = array_merge($filteredData, $newMediaItems);
613 }
614
615 $data['meta']['media_items'] = array_merge($filteredData, $data['meta']['media_items']);
616 }
617
618 if (isset($feed->meta['media_preview'])) {
619 if (count($mediaImages) > 1) {
620 $data['meta']['media_preview'] = null;
621 }
622 }
623
624 return $mediaItems ?? [];
625 }
626
627 private function generateMediaPreviews($mediaItems)
628 {
629 $mediaPreviews = [];
630 foreach ($mediaItems as $media) {
631 if (!$media || !$media->is_active) {
632 $this->sendError(['message' => 'Invalid media image. Please upload a new one.']);
633 }
634
635 $data = [
636 'media_id' => $media->id,
637 'url' => $media->public_url,
638 'type' => 'image',
639 'width' => Arr::get($media->settings, 'width'),
640 'height' => Arr::get($media->settings, 'height'),
641 'provider' => Arr::get($media->settings, 'provider', 'uploader')
642 ];
643
644 $mediaPreviews[] = array_filter($data);
645 }
646
647 return $mediaPreviews;
648 }
649
650 private function formatMediaMeta($mediaPreviews, &$data, $mediaImages)
651 {
652 $giphyImages = Helper::getMediaByProvider($mediaImages, 'giphy');
653 $metaMediaItems = Helper::getMediaByProvider($this->request->get('meta.media_items', []), 'giphy');
654
655 if (count($mediaPreviews) === 1 && empty($giphyImages) && empty($metaMediaItems)) {
656 $mediaPreview = array_filter([
657 'is_uploaded' => true,
658 'image' => $mediaPreviews[0]['url'],
659 'type' => 'meta_data',
660 'width' => Arr::get($mediaPreviews[0], 'width'),
661 'height' => Arr::get($mediaPreviews[0], 'height')
662 ]);
663
664 $data['meta']['media_preview'] = $mediaPreview;
665 } elseif ($mediaPreviews) {
666 $data['meta']['media_items'] = $mediaPreviews;
667 }
668 }
669
670 private function processGiphyImages($requestData, &$data)
671 {
672 if (!isset($data['meta']['media_items'])) {
673 $data['meta']['media_items'] = null;
674 }
675
676 if ($metaMediaItems = Arr::get($requestData, 'meta.media_items', [])) {
677 $giphyMediaItems = Helper::getMediaByProvider($metaMediaItems, 'giphy');
678
679 if ($giphyMediaItems) {
680 $data['meta']['media_items'] = array_merge($giphyMediaItems, (array)$data['meta']['media_items']);
681 }
682 }
683
684 if ($giphyImages = Helper::getMediaByProvider(Arr::get($requestData, 'media_images', []), 'giphy')) {
685
686 foreach ($giphyImages as $giphy) {
687 $data['meta']['media_items'][] = [
688 'url' => $giphy['url'],
689 'type' => 'image',
690 'provider' => 'giphy'
691 ];
692 }
693 }
694 }
695
696 private function parseFirstUrl($messageRendered)
697 {
698 $firstUrl = FeedsHelper::findFirstUrl($messageRendered);
699
700 if ($firstUrl) {
701 $metaData = RemoteUrlParser::parse($firstUrl);
702 if ($metaData && !is_wp_error($metaData) && (!empty($metaData['image']) || !empty($metaData['html']))) {
703 return [
704 'media_preview' => $metaData
705 ];
706 }
707 }
708
709 return [];
710 }
711
712 private function saveMediaItems($feed, $mediaItems)
713 {
714 foreach ($mediaItems as $media) {
715 $media->feed_id = $feed->id;
716 $media->is_active = 1;
717 $media->object_source = 'feed';
718 $media->save();
719 }
720 }
721
722 private function handleMentions($feed, $mentions)
723 {
724 if ($mentions) {
725 do_action('fluent_community/feed_mentioned', $feed, $mentions['users']);
726 }
727 }
728
729 private function syncHashTags($feed, $message)
730 {
731 if ($feed->id) {
732 do_action('fluent_community/feed/hashtags_deleted', $feed->terms);
733 }
734
735 $hashTags = FeedsHelper::extractHashTags($message);
736 if ($hashTags) {
737 $feed->terms()->sync($hashTags);
738 }
739 }
740
741 private function sanitizeAndValidateData($data)
742 {
743 $message = CustomSanitizer::unslashMarkdown(trim(Arr::get($data, 'message')));
744 $type = sanitize_text_field(Arr::get($data, 'type', 'text'));
745
746 $processedData = [
747 'message' => $message,
748 'type' => $type
749 ];
750
751 $survey = Arr::get($data, 'survey', []);
752
753 if ($survey) {
754 $options = Arr::get($survey, 'options', []);
755 $formattedOptions = [];
756 foreach ($options as $index => $option) {
757 if (empty($option['label'])) {
758 continue;
759 }
760
761 $formattedOptions[] = [
762 'label' => sanitize_text_field($option['label']),
763 'slug' => 'opt_' . ($index + 1)
764 ];
765 }
766
767 if ($formattedOptions) {
768 $processedData['survey'] = [
769 'type' => Arr::get($survey, 'type') == 'single_choice' ? 'single_choice' : 'multi_choice',
770 'options' => $formattedOptions
771 ];
772 }
773 }
774
775 $this->validate($processedData, [
776 'message' => 'required|min:10',
777 'type' => 'required'
778 ]);
779
780 $maxlen = apply_filters('fluent_community/max_post_length', 15000);
781 if (\strlen($message) > $maxlen) {
782 throw new \Exception(esc_html__('Post message is too long', 'fluent-community'));
783 }
784
785 $titlePref = Utility::postTitlePref();
786
787 if ($titlePref) {
788 $processedData['title'] = sanitize_text_field(Arr::get($data, 'title'));
789 if ($titlePref == 'required' && empty($processedData['title'])) {
790 throw new \Exception(esc_html__('Title is required. Please provide a title', 'fluent-community'));
791 }
792 // trim the title if it's too long to 150 char
793 if (\strlen($processedData['title']) > 192) {
794 $processedData['title'] = substr($processedData['title'], 0, 192);
795 }
796 }
797
798 return $processedData;
799 }
800
801 private function checkForDuplicatePost($userId, $message)
802 {
803 $message = trim($message);
804
805 $exist = Feed::where('user_id', $userId)
806 ->where('message', $message)
807 ->where('created_at', '>', gmdate('Y-m-d H:i:s', current_time('timestamp') - 7 * 24 * 60 * 60))
808 ->first();
809
810 if ($exist) {
811 return $this->sendError(['message' => 'No duplicate post please!']);
812 }
813
814 return false;
815 }
816
817 private function validateAndSetSpace($spaceSlug, $user)
818 {
819 if ($spaceSlug == '__self__post__') {
820 if (!Helper::hasGlobalPost()) {
821 throw new \Exception(__('Please select a valid space to post in', 'fluent-community'));
822 }
823
824 return null;
825 }
826
827 $space = Space::where('slug', $spaceSlug)->first();
828
829 if (!$space) {
830 throw new \Exception(__('Please select a valid space to post in', 'fluent-community'));
831 }
832
833 $user->verifySpacePermission('can_create_post', $space);
834
835 return $space->id;
836 }
837
838 public function deleteFeed(Request $request, $feed_id)
839 {
840 $feed = Feed::findOrFail($feed_id);
841
842 $user = User::find(get_current_user_id());
843 $user->canDeleteFeed($feed, true);
844 do_action('fluent_community/feed/before_deleted', $feed);
845 $feed->delete();
846
847 do_action('fluent_community/feed/deleted', $feed_id);
848
849 return [
850 'message' => 'Feed has been deleted successfully'
851 ];
852 }
853
854 public function deleteMediaPreview(Request $request, $feed_id)
855 {
856 $feed = Feed::findOrFail($feed_id);
857
858 $user = User::find(get_current_user_id());
859 $user->canDeleteFeed($feed, true);
860
861 do_action('fluent_community/feed/media_deleted', $feed->media);
862
863 $meta = $feed->meta;
864
865 $meta['media_preview'] = null;
866
867 $feed->meta = $meta;
868 $feed->save();
869
870 return [
871 'message' => 'Media preview has been removed successfully'
872 ];
873 }
874
875 public function addComment(Request $request, $feed_id)
876 {
877 $feed = Feed::findOrFail($feed_id);
878 $text = trim($request->getSafe('comment', 'sanitize_textarea_field', ''));
879
880 if (!$text) {
881 return $this->sendError([
882 'message' => 'Please provide your reply text'
883 ]);
884 }
885
886 // check for duplicate
887 $exist = Comment::where('user_id', get_current_user_id())
888 ->where('message', $text)
889 ->where('post_id', $feed->id)
890 ->first();
891
892 if ($exist) {
893 return $this->sendError([
894 'message' => 'No duplicate comment please!'
895 ]);
896 }
897
898 if ($feed->space_id) {
899 $user = User::find(get_current_user_id());
900 $user->verifySpacePermission('registered', $feed->space);
901 }
902
903 $commentData = [
904 'post_id' => $feed->id,
905 'message' => $text,
906 'message_rendered' => wp_kses_post(FeedsHelper::mdToHtml($text)),
907 'type' => 'comment'
908 ];
909
910 if ($request->get('parent_id')) {
911 $parentId = (int)$request->get('parent_id');
912
913 // verify the parent id
914 $parentComment = Comment::where('id', $parentId)
915 ->where('post_id', $feed->id)
916 ->first();
917
918 if (!$parentComment || $parentComment->post_id != $feed->id) {
919 return $this->sendError([
920 'message' => 'Invalid parent comment'
921 ]);
922 }
923
924 $commentData['parent_id'] = $parentId;
925 }
926
927 $comment = Comment::create($commentData);
928
929 $feed->comments_count = $feed->comments_count + 1;
930 $feed->save();
931
932 $comment->load([
933 'xprofile' => function ($q) {
934 $q->select(ProfileHelper::getXProfilePublicFields());
935 }
936 ]);
937
938 return [
939 'comment' => $comment,
940 'message' => 'Comment has been added'
941 ];
942 }
943
944 public function addOrRemovePostReact(Request $request, $feed_id)
945 {
946
947 $feed = Feed::byUserAccess(get_current_user_id())->findOrFail($feed_id);
948
949 $type = $request->get('react_type', 'like');
950 $willRemove = $request->get('remove');
951 $react = Reaction::where('user_id', get_current_user_id())
952 ->where('object_id', $feed->id)
953 ->where('type', $type)
954 ->objectType('feed')
955 ->first();
956
957 if ($willRemove) {
958 if ($react) {
959 $react->delete();
960 $feed->reactions_count = $feed->reactions_count - 1;
961 $feed->save();
962 }
963
964 return [
965 'message' => 'Reaction has been removed',
966 'new_count' => $feed->reactions_count
967 ];
968 }
969
970 if ($react) {
971 return [
972 'message' => 'You have already reacted to this post',
973 'new_count' => $feed->reactions_count
974 ];
975 }
976
977 $react = Reaction::create([
978 'user_id' => get_current_user_id(),
979 'object_id' => $feed->id,
980 'type' => $type,
981 'object_type' => 'feed'
982 ]);
983
984 $feed->reactions_count = $feed->reactions_count + 1;
985 $feed->save();
986
987 return [
988 'message' => 'Reaction has been added',
989 'new_count' => $feed->reactions_count
990 ];
991 }
992
993 public function handleMediaUpload(Request $request)
994 {
995 $allowedTypes = implode(
996 ',',
997 apply_filters('fluent_community/support_attachment_types', [
998 'image/jpeg',
999 'image/pjpeg',
1000 'image/jpeg',
1001 'image/pjpeg',
1002 'image/png',
1003 'image/gif',
1004 'image/webp'
1005 ])
1006 );
1007
1008 $files = $this->validate($this->request->files(), [
1009 'file' => 'mimetypes:' . $allowedTypes,
1010 // 'source' => 'required|in:feed,avatar,comment,cover,space'
1011 ], [
1012 'file.mimetypes' => __('The file must be a image type.', 'fluent-community')
1013 ]);
1014
1015 $uploadedFiles = FileSystem::put($files);
1016
1017 $file = $uploadedFiles[0];
1018
1019 $willWebPConvert = apply_filters('fluent_community/convert_image_to_webp', true, $file);
1020
1021 if ($request->get('resize') && $maxWidth = $request->get('max_width')) {
1022 $upload_dir = wp_upload_dir();
1023 $fileUrl = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file['url']);
1024 $editor = wp_get_image_editor($fileUrl);
1025 if (!is_wp_error($editor) && $editor->get_size()['width'] > $maxWidth) {
1026 // Current file extension
1027 $ext = pathinfo($file['url'], PATHINFO_EXTENSION);
1028 $imageExtensions = ['jpg', 'jpeg', 'png', 'gif'];
1029
1030 $willConvert = in_array($ext, $imageExtensions) && $willWebPConvert;
1031
1032 if ($willConvert) {
1033 $imageExtensions = array_map(function ($ext) {
1034 return '.' . $ext;
1035 }, $imageExtensions);
1036 $fileUrl = str_replace($imageExtensions, '.webp', $fileUrl);
1037 $file['file'] = str_replace($imageExtensions, '.webp', $file['file']);
1038 $file['url'] = str_replace($imageExtensions, '.webp', $file['url']);
1039 $file['type'] = 'image/webp';
1040 }
1041
1042 // resize the image
1043 $editor->resize($maxWidth, null, false);
1044 $editor->set_quality(90);
1045 if ($willConvert) {
1046 $editor->save($fileUrl, 'image/webp');
1047 // remove original file now
1048 wp_delete_file(str_replace('.webp', '.' . $ext, $fileUrl));
1049 $file['is_converted'] = true;
1050 } else {
1051 $editor->save($fileUrl);
1052 }
1053
1054 $file['meta'] = [
1055 'width' => $editor->get_size()['width'],
1056 'height' => $editor->get_size()['height']
1057 ];
1058 }
1059 $file['path'] = $upload_dir['basedir'] . '/fluent-community/' . $file['file'];
1060 } else {
1061 $upload_dir = wp_upload_dir();
1062 $file['path'] = $upload_dir['basedir'] . '/fluent-community/' . $file['file'];
1063 }
1064
1065 if ($willWebPConvert && empty($file['is_converted']) && !$request->get('skip_convert')) {
1066 $path = $file['path'];
1067 $extension = pathinfo($path, PATHINFO_EXTENSION);
1068
1069 $convertFromExtensions = ['png', 'jpg', 'jpeg', 'gif'];
1070 if ($extension != 'webp' && in_array($extension, $convertFromExtensions)) {
1071 // Let's convert to webp
1072 $editor = wp_get_image_editor($file['path']);
1073 if (!is_wp_error($editor)) {
1074 $orginalPath = $file['path'];
1075 $file['path'] = str_replace('.' . $extension, '.webp', $file['path']);
1076 $file['url'] = str_replace('.' . $extension, '.webp', $file['url']);
1077 $file['type'] = 'image/webp';
1078 $editor->save($file['path'], 'image/webp');
1079 wp_delete_file($orginalPath);
1080
1081 $file['meta'] = [
1082 'width' => $editor->get_size()['width'],
1083 'height' => $editor->get_size()['height']
1084 ];
1085 }
1086 }
1087 }
1088
1089 $mediaData = apply_filters('fluent_community/media_upload_data', [
1090 'media_type' => $file['type'],
1091 'driver' => 'local',
1092 'media_path' => $file['path'],
1093 'media_url' => $file['url'],
1094 'settings' => Arr::get($file, 'meta', [])
1095 ], $file);
1096
1097 // Let's create the media now
1098 $media = Media::create($mediaData);
1099
1100 $mediaUrl = $media->public_url;
1101
1102 $mediaUrl = add_query_arg([
1103 'media_key' => $media->media_key,
1104 ], $mediaUrl);
1105
1106 return [
1107 'media' => [
1108 'url' => $mediaUrl,
1109 'media_key' => $media->media_key,
1110 'type' => $media->media_type,
1111 'width' => Arr::get($media->settings, 'width'),
1112 'height' => Arr::get($media->settings, 'height')
1113 ]
1114 ];
1115 }
1116
1117 public function getTicker(Request $request)
1118 {
1119 do_action('fluent_communit/track_activity');
1120
1121 $lastId = $request->get('last_feed_id');
1122
1123 $newItemsCount = Feed::where('id', '>', $lastId)
1124 ->where('status', 'published')
1125 ->byUserAccess(get_current_user_id())
1126 ->count();
1127
1128 $notificationCount = NotificationSubscriber::unread()->where('user_id', get_current_user_id())->count();
1129
1130 return [
1131 'new_items_count' => $newItemsCount,
1132 'last_checked' => current_time('mysql'),
1133 'unread_notification_count' => $notificationCount
1134 ];
1135 }
1136
1137 public function getOembed(Request $request)
1138 {
1139 $url = $request->get('url');
1140 // check if the url is valid
1141 $metaData = RemoteUrlParser::parse($url);
1142
1143 if ($metaData && !is_wp_error($metaData)) {
1144 return [
1145 'oembed' => $metaData
1146 ];
1147 }
1148
1149 return $this->send([
1150 'message' => 'No oembed data found',
1151 'url' => $url
1152 ]);
1153 }
1154
1155 public function markdownToHtml(Request $request)
1156 {
1157 $message = trim(sanitize_textarea_field($request->get('text', '')));
1158
1159 $html = FeedsHelper::mdToHtml($message);
1160
1161 return [
1162 'html' => $html
1163 ];
1164 }
1165
1166 private function transformFeed(Feed $feed)
1167 {
1168 $userId = $this->getUserId();
1169 if ($userId) {
1170 $feed->has_user_react = $feed->hasUserReact($userId, 'like');
1171 $feed->bookmarked = $feed->hasUserReact($userId, 'bookmark');
1172
1173 $likedIds = FeedsHelper::getLikedIdsByUserFeedId($feed->id, get_current_user_id());
1174 $feed->comments->each(function ($comment) use ($likedIds) {
1175 if ($likedIds && in_array($comment->id, $likedIds)) {
1176 $comment->liked = 1;
1177 }
1178 });
1179
1180 if ($feed->content_type == 'survey') {
1181 $votedOptions = $feed->getSurveyCastsByUserId($userId);
1182
1183 if ($votedOptions) {
1184 $surveyConfig = Arr::get($feed->meta, 'survey_config', []);
1185 foreach ($surveyConfig['options'] as $index => $option) {
1186 if (in_array($option['slug'], $votedOptions)) {
1187 $surveyConfig['options'][$index]['voted'] = true;
1188 }
1189 }
1190 $meta = $feed->meta;
1191 $meta['survey_config'] = $surveyConfig;
1192 $feed->meta = $meta;
1193 }
1194 }
1195 }
1196
1197 return $feed;
1198 }
1199 }
1200