PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.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 / BaseSpace.php

BaseSpace.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.10.01, at app/Models/BaseSpace.php

676 lines 19.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\App;
6 use FluentCommunity\App\Functions\Utility;
7 use FluentCommunity\App\Services\CustomSanitizer;
8 use FluentCommunity\App\Services\LockscreenService;
9 use FluentCommunity\App\Services\Helper;
10 use FluentCommunity\Framework\Support\Arr;
11
12 /**
13 * @property int $id
14 * @property int|null $created_by
15 * @property int|null $parent_id
16 * @property string $title
17 * @property string $slug
18 * @property string|null $description
19 * @property string|null $logo
20 * @property string|null $cover_photo
21 * @property string|null $type
22 * @property string|null $privacy
23 * @property string|null $status
24 * @property int|null $serial
25 * @property array $settings
26 * @property string|null $created_at
27 * @property string|null $updated_at
28 * @property array|null $permissions
29 * @property string|null $description_rendered
30 * @property mixed $membership
31 * @property array|null $topics
32 * @property array|null $header_links
33 * @property array|null $lockscreen_config
34 */
35 class BaseSpace extends Model
36 {
37 protected $table = 'fcom_spaces';
38
39 protected static $type = 'community';
40
41 protected $guarded = [ 'id' ];
42
43 protected $fillable = [
44 'created_by',
45 'parent_id',
46 'title',
47 'slug',
48 'description',
49 'logo',
50 'cover_photo',
51 'type',
52 'privacy',
53 'status',
54 'serial',
55 'settings',
56 ];
57
58 protected $searchable = [
59 'title',
60 'description',
61 ];
62
63 public $_preloadedMembershipUserId = null;
64 public $_preloadedMembership = null;
65
66 public static function boot()
67 {
68 parent::boot();
69
70 static::creating(function ($model) {
71 if (empty($model->created_by)) {
72 $model->created_by = get_current_user_id();
73 }
74
75 if (empty($model->slug)) {
76 $model->slug = self::generateNewSlug($model);
77 }
78
79 $model->type = static::$type;
80 });
81
82 static::addGlobalScope('type', function ($query) {
83 if (static::$type) {
84 $query->where('type', static::$type);
85 }
86
87 return $query;
88 });
89
90 static::deleting(function ($space) {
91 Media::where('sub_object_id', $space->id)
92 ->whereIn('object_source', [ 'space_logo', 'space_cover_photo' ])
93 ->update([
94 'is_active' => 0,
95 ]);
96 });
97 }
98
99 public function owner()
100 {
101 return $this->belongsTo(User::class, 'created_by', 'ID');
102 }
103
104 public function space_pivot()
105 {
106 return $this->belongsTo(SpaceUserPivot::class, 'id', 'space_id');
107 }
108
109 public function admins()
110 {
111 return $this->belongsToMany(User::class, 'fcom_space_user', 'space_id', 'user_id')
112 ->where(function ($query) {
113 $query->where('role', 'admin')
114 ->orWhere('role', 'moderator');
115 });
116 }
117
118 public function scopeSearchBy($query, $search)
119 {
120 if ($search) {
121 $fields = $this->searchable;
122 $query->where(function ($query) use ($fields, $search) {
123 $query->where(array_shift($fields), 'LIKE', "%$search%");
124 foreach ($fields as $field) {
125 $query->orWhere($field, 'LIKE', "$search%");
126 }
127 });
128 }
129
130 return $query;
131 }
132
133 public function scopeOnlyMain($query)
134 {
135 return $query->withoutGlobalScopes()->whereIn('type', [ 'community', 'course' ]);
136 }
137
138 public function scopeFilterByUserId($query, $userId)
139 {
140 if (!$userId) {
141 return $query->where('privacy', 'public');
142 }
143
144 $ids = get_user_meta($userId, '_fcom_space_ids', true);
145
146 if (!$ids) {
147 return $query->where('privacy', 'public');
148 }
149
150 return $query->whereIn('id', $ids);
151 }
152
153 public function scopeByUserAccess($query, $userId)
154 {
155 if (!$userId) {
156 return $query->where('privacy', 'public');
157 }
158
159 return $this->where(function ($query) use ($userId) {
160 return $query->where('privacy', 'public')
161 ->orWhereHas('members', function ($query) use ($userId) {
162 return $query->where('user_id', $userId);
163 });
164 });
165 }
166
167 public function posts()
168 {
169 return $this->hasMany(Feed::class, 'space_id', 'id');
170 }
171
172 public function comments()
173 {
174 return $this->hasManyThrough(Comment::class, Feed::class, 'space_id', 'post_id');
175 }
176
177 public function members()
178 {
179 return $this->belongsToMany(User::class, 'fcom_space_user', 'space_id', 'user_id')
180 ->withPivot([ 'role', 'created_at', 'status' ]);
181 }
182
183 public function x_members()
184 {
185 return $this->belongsToMany(XProfile::class, 'fcom_space_user', 'space_id', 'user_id', 'id', 'user_id')
186 ->withPivot([ 'role', 'created_at', 'status' ]);
187 }
188
189 public function group()
190 {
191 return $this->belongsTo(SpaceGroup::class, 'parent_id', 'id');
192 }
193
194 public function getMembership($userId)
195 {
196 if (!$userId) {
197 return null;
198 }
199
200 if ($this->_preloadedMembershipUserId == $userId) {
201 return $this->_preloadedMembership;
202 }
203
204 return $this->members()->where('user_id', $userId)->first();
205 }
206
207 public static function preloadMemberships($spaces, $userId)
208 {
209 if (!$userId || $spaces->isEmpty()) {
210 return;
211 }
212
213 $spaceIds = $spaces->pluck('id')->toArray();
214
215 $pivots = SpaceUserPivot::where('user_id', $userId)
216 ->whereIn('space_id', $spaceIds)
217 ->get()
218 ->keyBy('space_id');
219
220 foreach ($spaces as $space) {
221 $space->_preloadedMembershipUserId = $userId;
222 $pivot = $pivots->get($space->id);
223 $space->_preloadedMembership = $pivot ? self::pivotToMembership($pivot) : null;
224 }
225 }
226
227 private static function pivotToMembership($pivot)
228 {
229 $membership = new \stdClass();
230 $membership->pivot = (object) $pivot->toArray();
231 $membership->ID = $pivot->user_id;
232 return $membership;
233 }
234
235 public function isCourseSpace()
236 {
237 return $this->type == 'course';
238 }
239
240 public function isAdmin($userId, $checkModerator = false)
241 {
242 if (Helper::isSiteAdmin($userId)) {
243 return true;
244 }
245
246 $roles = [ 'admin' ];
247
248 if ($checkModerator) {
249 if (Helper::isModerator()) {
250 return true;
251 }
252 $roles[] = 'moderator';
253 }
254
255 $membership = $this->getMembership($userId);
256 if (!$membership) {
257 return false;
258 }
259
260 return in_array($membership->pivot->role, $roles);
261 }
262
263 public function updateCustomData($data, $removeSrc = false)
264 {
265 $deletePhotos = [];
266 if (isset($data['logo'])) {
267 $deletePhotos[] = $this->logo;
268 $this->logo = sanitize_url($data['logo']);
269 }
270
271 if (isset($data['cover_photo'])) {
272 $deletePhotos[] = $this->cover_photo;
273 $this->cover_photo = sanitize_url($data['cover_photo']);
274 }
275
276 if (isset($data['description'])) {
277 $this->description = wp_kses_post($data['description']);
278 }
279
280 if (isset($data['title'])) {
281 $this->title = sanitize_text_field($data['title']);
282 }
283
284 if (isset($data['privacy'])) {
285 $this->privacy = sanitize_text_field($data['privacy']);
286 }
287
288 if (isset($data['parent_id'])) {
289 if (!empty($data['parent_id'])) {
290 $group = SpaceGroup::find($data['parent_id']);
291 if (!$group) {
292 throw new \Exception(esc_html__('Invalid group id', 'fluent-community'), 400);
293 }
294 $this->parent_id = $group->id;
295 } else {
296 $this->parent_id = null;
297 }
298 }
299
300 if (isset($data['settings'])) {
301
302 $settings = CustomSanitizer::santizeSpaceSettings($data['settings'], $this->privacy);
303
304 if (is_wp_error($settings)) {
305 return $settings;
306 }
307
308 $exisitingSetting = $this->settings;
309 $settings['links'] = Arr::get($exisitingSetting, 'links', []);
310
311 if (isset($settings['og_image'])) {
312 $ogImageUrl = Arr::get($settings, 'og_image');
313 if ($ogImageUrl) {
314 $ogImageMedia = Helper::getMediaFromUrl($ogImageUrl);
315 if ($ogImageMedia && $ogImageMedia->is_active) {
316 unset($settings['og_image']);
317 } elseif ($ogImageMedia) {
318 $ogImageMedia->update([
319 'is_active' => true,
320 'user_id' => get_current_user_id(),
321 'sub_object_id' => $this->id,
322 'object_source' => 'space_og_media',
323 ]);
324 $settings['og_image'] = $ogImageMedia->public_url;
325 } else {
326 $settings['og_image'] = sanitize_url($ogImageUrl);
327 }
328 }
329 } else {
330 $deletePhotos[] = Arr::get($exisitingSetting, 'og_image');
331 }
332
333 $settings = wp_parse_args($settings, $exisitingSetting);
334 $this->settings = $settings;
335 }
336
337 if (!empty($data['slug']) && $data['slug'] !== $this->slug) {
338 $newSlug = preg_replace('/[^a-zA-Z0-9-_]/', '', $data['slug']);
339
340 $newSlug = sanitize_title($newSlug);
341
342 if (empty($newSlug)) {
343 throw new \Exception('Invalid slug', 400);
344 }
345
346 $exist = App::getInstance('db')->table('fcom_spaces')->where('slug', $newSlug)
347 ->where('id', '!=', $this->id)
348 ->exists();
349
350 if ($exist) {
351 throw new \Exception(esc_html__('Slug already exists. Please use a different slug', 'fluent-community'), 400);
352 }
353
354 $this->slug = $newSlug;
355 }
356
357 $deletePhotos = array_filter($deletePhotos);
358 if ($removeSrc && $deletePhotos) {
359 $deletePhotos = array_filter($deletePhotos);
360 do_action('fluent_community/remove_medias_by_url', $deletePhotos, [
361 'sub_object_id' => $this->id,
362 ]);
363 }
364
365 $dirty = $this->getDirty();
366
367 if ($dirty) {
368 $this->save();
369 if ($this->type == 'community') {
370 do_action('fluent_community/space/updated', $this, $dirty);
371 }
372 }
373
374 return $this;
375 }
376
377 public function isContentSpace()
378 {
379 return in_array($this->type, [ 'community', 'course' ]);
380 }
381
382 public function getSettingsAttribute($value)
383 {
384 $settings = Utility::safeUnserialize($value);
385
386 if (!$settings) {
387 $settings = [];
388 }
389
390 $settings = wp_parse_args($settings, $this->defaultSettings());
391
392 if (!defined('FLUENT_COMMUNITY_PRO')) {
393 $settings['document_library'] = 'no';
394 $settings['media_gallery'] = 'no';
395 }
396
397 return $settings;
398 }
399
400 public function defaultSettings()
401 {
402 return [
403 'restricted_post_only' => 'no',
404 'emoji' => '',
405 'shape_svg' => '',
406 'custom_lock_screen' => 'no',
407 'can_request_join' => 'no',
408 'layout_style' => 'timeline',
409 'show_sidebar' => 'yes',
410 'og_image' => '',
411 'links' => [],
412 'topic_required' => 'no',
413 'hide_members_count' => 'no', // yes / no
414 'members_page_status' => 'members_only', // members_only, everybody, logged_in, admin_only
415 ];
416 }
417
418 public function setSettingsAttribute($value)
419 {
420 $this->attributes['settings'] = maybe_serialize($value);
421 }
422
423 public function getPublicPermissions()
424 {
425 if ($this->privacy == 'public') {
426
427 $hasDocuments = defined('FLUENT_COMMUNITY_PRO') && Arr::get($this->settings, 'document_library') == 'yes';
428 $hasMediaGallery = defined('FLUENT_COMMUNITY_PRO') && Arr::get($this->settings, 'media_gallery') == 'yes';
429
430 return [
431 'can_view_info' => true,
432 'can_view_posts' => true,
433 'can_view_members' => $this->canViewMembers(null),
434 'can_create_post' => false,
435 'can_view_documents' => $hasDocuments && Arr::get($this->settings, 'document_access') == 'everybody',
436 'can_view_media' => $hasMediaGallery && Arr::get($this->settings, 'media_access') == 'everybody',
437 ];
438 }
439
440 return [
441 'can_view_info' => false,
442 'can_view_posts' => false,
443 ];
444 }
445
446 public function getUserPermissions($user = null)
447 {
448 if (!$user) {
449 return $this->getPublicPermissions();
450 }
451
452 return $user->getSpacePermissions($this);
453 }
454
455 public function canViewMembers($user)
456 {
457 $viewStatus = Arr::get($this->settings, 'members_page_status');
458 if ($viewStatus === 'everybody') {
459 return true;
460 }
461
462 if (!$user) {
463 return false;
464 }
465
466 if ($viewStatus === 'logged_in') {
467 return true;
468 }
469
470 if ($viewStatus === 'members_only') {
471 $membership = $this->getMembership($user->ID);
472 return $membership && isset($membership->pivot) && $membership->pivot->status === 'active';
473 }
474
475 return $this->isAdmin($user->ID, true);
476 }
477
478 public function verifyUserPermisson($user, $permission, $exception = true)
479 {
480 $permissions = $this->getUserPermissions($user);
481
482 $hasPermission = $permissions[$permission] ?? false;
483
484 if (!$hasPermission && $exception) {
485 /* translators: %s is the permission name */
486 throw new \Exception(esc_html(sprintf(__('Sorry you do not have permission %s', 'fluent-community'), $permission)), 403);
487 }
488
489 return $hasPermission;
490 }
491
492 public function setLockscreen($settingFields)
493 {
494 return $this->updateCustomMeta('lockscreen_settings', $settingFields);
495 }
496
497 public function getPermalink()
498 {
499 if ($this->type == 'community') {
500 return Helper::baseUrl('space/' . $this->slug . '/home');
501 }
502
503 if ($this->type == 'course') {
504 return Helper::baseUrl('course/' . $this->slug . '/lessons');
505 }
506
507 if ($this->type == 'sidebar_link') {
508 $permalink = Arr::get($this->settings, 'permalink');
509 if ($permalink) {
510 return $permalink;
511 }
512 }
513
514 return Helper::baseUrl('/');
515 }
516
517 public function getLockscreen()
518 {
519 return LockscreenService::getLockscreenSettings($this);
520 }
521
522 public function hasPaywallIntegration()
523 {
524 return !empty(Arr::get($this->settings, 'cart_product_ids', []));
525 }
526
527 public function getCustomMeta($key, $default = null)
528 {
529 return Helper::getSpaceMeta($this->id, $key, $default);
530 }
531
532 public function updateCustomMeta($key, $value)
533 {
534 return Helper::updateSpaceMeta($this->id, $key, $value);
535 }
536
537 public function getIconMark($isHtml = true)
538 {
539 if (!$isHtml) {
540 return '';
541 }
542
543 if ($this->logo) {
544 return '<img alt="" src="' . esc_url($this->logo) . '"/>';
545 }
546
547 if ($imoji = Arr::get($this->settings, 'emoji')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
548 return '<span class="fcom_emoji">' . $imoji . '</span>';
549 }
550
551 if ($svg = Arr::get($this->settings, 'shape_svg')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
552 return '<span class="fcom_shape"><i class="el-icon">' . $svg . '</i></span>';
553 }
554
555 return '';
556 }
557
558 public function syncTopics($topicIds)
559 {
560 $topics = Term::where('taxonomy_name', 'post_topic')->whereIn('id', $topicIds)->get();
561 $topicIds = $topics->pluck('id')->toArray();
562 $existIds = [];
563
564 foreach ($topicIds as $topicId) {
565 $relation = Meta::where('object_id', $topicId)
566 ->where('object_type', 'term_space_relation')
567 ->where('meta_key', $this->id)
568 ->first();
569
570 if ($relation) {
571 $existIds[] = $relation->id;
572 continue;
573 }
574
575 // We have to create one
576 $new = Meta::create([
577 'object_id' => $topicId,
578 'meta_key' => $this->id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
579 'object_type' => 'term_space_relation',
580 ]);
581
582 $existIds[] = $new->id;
583 }
584
585 // Remove other relations
586 Meta::whereNotIn('id', $existIds)
587 ->where('object_type', 'term_space_relation')
588 ->where('meta_key', $this->id)
589 ->delete();
590
591 Utility::forgetCache('fluent_community_post_topics');
592
593 return $this;
594 }
595
596 protected static function generateNewSlug($newModel)
597 {
598 if ($newModel->title) {
599 // Remove the emojis
600 $title = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $newModel->title);
601 // get the first 30 char from the title
602 $title = mb_substr($title, 0, 30, 'UTF-8');
603 } else {
604 $title = static::$type . '-' . time();
605 }
606
607 $title = Helper::normalizeToAscii($title);
608 $title = remove_accents($title);
609
610 $title = strtolower($title);
611
612 $title = trim(preg_replace('/[^a-z0-9-_]/', ' ', $title));
613
614 $slugNum = self::withoutGlobalScopes()->where('type', static::$type)->count();
615
616 $title = sanitize_title($title, static::$type . '-' . $slugNum);
617
618 // check if the slug is already exists
619 $slug = $title;
620 $count = 1;
621 while (self::withoutGlobalScopes()->where('slug', $slug)->exists()) {
622 if ($count == 5) {
623 $count = time();
624 }
625 $slug = $title . '-' . (++$slugNum);
626 ++$count;
627 }
628
629 return $slug;
630 }
631
632 public function formatSpaceData($user)
633 {
634 $userId = $user ? $user->ID : null;
635
636 $this->permissions = $this->getUserPermissions($user);
637 $this->description_rendered = wpautop($this->description);
638 $this->membership = $this->getMembership($userId);
639 $this->topics = Utility::getTopicsBySpaceId($this->id);
640
641 $headerLinks = [
642 [
643 'title' => __('Posts', 'fluent-community'),
644 'route' => [
645 'name' => 'space_feeds',
646 ],
647 ],
648 ];
649
650 if (Arr::get($this->permissions, 'can_view_members')) {
651 $headerLinks[] = [
652 'title' => __('Members', 'fluent-community'),
653 'route' => [
654 'name' => 'space_members',
655 ],
656 ];
657 }
658
659 $this->header_links = apply_filters('fluent_community/space_header_links', $headerLinks, $this);
660
661 if ($this->isAdmin($userId, true)) {
662 return $this;
663 }
664
665 $this->lockscreen_config = LockscreenService::getLockscreenConfig($this, $this->membership, true);
666
667 $spaceSettings = $this->settings;
668
669 $spaceSettings['links'] = Helper::filterAccessibleLinks(Arr::get($spaceSettings, 'links', []), $user);
670
671 $this->settings = $spaceSettings;
672
673 return $this;
674 }
675 }
676