| 1 |
<?php |
| 2 |
|
| 3 |
namespace FluentCommunity\App\Services; |
| 4 |
|
| 5 |
use FluentCommunity\App\App; |
| 6 |
use FluentCommunity\App\Functions\Utility; |
| 7 |
use FluentCommunity\App\Hooks\Handlers\ActivationHandler; |
| 8 |
use FluentCommunity\App\Models\BaseSpace; |
| 9 |
use FluentCommunity\App\Models\Space; |
| 10 |
use FluentCommunity\App\Models\Media; |
| 11 |
use FluentCommunity\App\Models\Meta; |
| 12 |
use FluentCommunity\App\Models\SpaceUserPivot; |
| 13 |
use FluentCommunity\App\Models\User; |
| 14 |
use FluentCommunity\App\Models\XProfile; |
| 15 |
use FluentCommunity\Framework\Support\Arr; |
| 16 |
use FluentCommunity\App\Models\SpaceGroup; |
| 17 |
|
| 18 |
/** |
| 19 |
* Helper class for various utility functions. |
| 20 |
*/ |
| 21 |
class Helper |
| 22 |
{ |
| 23 |
|
| 24 |
public static function isRtl() |
| 25 |
{ |
| 26 |
return apply_filters('fluent_community/is_rtl', is_rtl()); |
| 27 |
} |
| 28 |
|
| 29 |
/** |
| 30 |
* Get the portal slug. |
| 31 |
* |
| 32 |
* @return string The portal slug. |
| 33 |
*/ |
| 34 |
public static function getPortalSlug($forRoute = false) |
| 35 |
{ |
| 36 |
$settings = self::generalSettings(); |
| 37 |
$slug = apply_filters('fluent_community/portal_slug', $settings['slug']); |
| 38 |
|
| 39 |
if (!$forRoute) { |
| 40 |
return $slug; |
| 41 |
} |
| 42 |
|
| 43 |
$siteUrl = get_site_url(); |
| 44 |
|
| 45 |
$poralUrl = self::baseUrl('/'); |
| 46 |
|
| 47 |
// get the url without path |
| 48 |
$siteUrl = str_replace(parse_url($siteUrl, PHP_URL_PATH), '', $siteUrl); |
| 49 |
|
| 50 |
$slug = str_replace($siteUrl, '', $poralUrl); |
| 51 |
|
| 52 |
// remove the first and last slashes |
| 53 |
return trim($slug, '/'); |
| 54 |
} |
| 55 |
|
| 56 |
/** |
| 57 |
* Get the portal route type. |
| 58 |
* |
| 59 |
* @return string The portal route type. |
| 60 |
*/ |
| 61 |
public static function getPortalRouteType() |
| 62 |
{ |
| 63 |
return apply_filters('fluent_community/portal_route_type', 'WebHistory'); |
| 64 |
} |
| 65 |
|
| 66 |
/** |
| 67 |
* Check if the portal is headless. |
| 68 |
* |
| 69 |
* @return bool True if headless, false otherwise. |
| 70 |
*/ |
| 71 |
public static function isHeadless() |
| 72 |
{ |
| 73 |
return apply_filters('fluent_community/portal_page_headless', false); |
| 74 |
} |
| 75 |
|
| 76 |
/** |
| 77 |
* Check if the portal has a color scheme. |
| 78 |
* |
| 79 |
* @return bool True if has color scheme, false otherwise. |
| 80 |
*/ |
| 81 |
public static function hasColorScheme() |
| 82 |
{ |
| 83 |
$status = Utility::isCustomizationEnabled('dark_mode'); |
| 84 |
return apply_filters('fluent_community/has_color_scheme', $status); |
| 85 |
} |
| 86 |
|
| 87 |
/** |
| 88 |
* Check if the user is a site admin. |
| 89 |
* |
| 90 |
* @param int|null $userId The user ID to check. If null, checks the current user. |
| 91 |
* @return bool True if the user is a site admin, false otherwise. |
| 92 |
*/ |
| 93 |
public static function isSiteAdmin($userId = null) |
| 94 |
{ |
| 95 |
$capability = apply_filters('fluent_community/super_admin_capability', 'manage_options'); |
| 96 |
|
| 97 |
if (!$capability) { |
| 98 |
return false; |
| 99 |
} |
| 100 |
|
| 101 |
if ($userId === null) { |
| 102 |
$userId = get_current_user_id(); |
| 103 |
} |
| 104 |
|
| 105 |
if (!$userId) { |
| 106 |
return false; |
| 107 |
} |
| 108 |
|
| 109 |
if (!$userId) { |
| 110 |
return current_user_can($capability); |
| 111 |
} |
| 112 |
|
| 113 |
return user_can($userId, $capability); |
| 114 |
} |
| 115 |
|
| 116 |
/** |
| 117 |
* Get the URL for an asset file. |
| 118 |
* |
| 119 |
* @param string $file The file name. |
| 120 |
* @return string The full URL to the asset. |
| 121 |
*/ |
| 122 |
public static function assetUrl($file = '') |
| 123 |
{ |
| 124 |
return FLUENT_COMMUNITY_PLUGIN_URL . 'assets/' . $file; |
| 125 |
} |
| 126 |
|
| 127 |
/** |
| 128 |
* Get the base URL for the portal. |
| 129 |
* |
| 130 |
* @param string $path The path to append to the base URL. |
| 131 |
* @return string The full base URL. |
| 132 |
*/ |
| 133 |
public static function baseUrl($path = '') |
| 134 |
{ |
| 135 |
$baseUrl = apply_filters('fluent_community/base_url', home_url(self::getPortalSlug())); |
| 136 |
$baseUrl = rtrim($baseUrl, '/'); |
| 137 |
|
| 138 |
if (self::getPortalRouteType() != 'hash') { |
| 139 |
return $baseUrl . '/' . ltrim($path, '/'); |
| 140 |
} |
| 141 |
|
| 142 |
if (!$path) { |
| 143 |
return $baseUrl . '/'; |
| 144 |
} |
| 145 |
|
| 146 |
return $baseUrl . '/#/' . ltrim($path, '/'); |
| 147 |
} |
| 148 |
|
| 149 |
/** |
| 150 |
* Get the space IDs for a user. |
| 151 |
* |
| 152 |
* @param int|null $userId The user ID. If null, uses the current user. |
| 153 |
* @return array An array of space IDs. |
| 154 |
*/ |
| 155 |
public static function getUserSpaceIds($userId = null) |
| 156 |
{ |
| 157 |
if (!$userId) { |
| 158 |
$userId = get_current_user_id(); |
| 159 |
} |
| 160 |
|
| 161 |
return SpaceUserPivot::where('user_id', $userId) |
| 162 |
->where('status', 'active') |
| 163 |
->pluck('space_id') |
| 164 |
->toArray(); |
| 165 |
} |
| 166 |
|
| 167 |
/** |
| 168 |
* Check if a user is in a specific space. |
| 169 |
* |
| 170 |
* @param int $userId The user ID. |
| 171 |
* @param int $spaceId The space ID. |
| 172 |
* @return bool True if the user is in the space, false otherwise. |
| 173 |
*/ |
| 174 |
public static function isUserInSpace($userId, $spaceId) |
| 175 |
{ |
| 176 |
return SpaceUserPivot::where('user_id', $userId) |
| 177 |
->where('space_id', $spaceId) |
| 178 |
->exists(); |
| 179 |
} |
| 180 |
|
| 181 |
/** |
| 182 |
* Generate HTML attributes from an array. |
| 183 |
* |
| 184 |
* @param array $atts An array of attribute key-value pairs. |
| 185 |
* @return string The generated HTML attributes string. |
| 186 |
*/ |
| 187 |
public static function attrs($atts = []) |
| 188 |
{ |
| 189 |
$text = ''; |
| 190 |
|
| 191 |
foreach ($atts as $key => $value) { |
| 192 |
$text .= "$key=\"$value\" "; |
| 193 |
} |
| 194 |
|
| 195 |
return $text; |
| 196 |
} |
| 197 |
|
| 198 |
/** |
| 199 |
* Get media from a URL. |
| 200 |
* |
| 201 |
* @param string|array $url The URL or an array containing URL information. |
| 202 |
* @return Media|null The Media object if found, null otherwise. |
| 203 |
*/ |
| 204 |
public static function getMediaFromUrl($url) |
| 205 |
{ |
| 206 |
if (is_array($url) && isset($url['provider'])) { |
| 207 |
$provider = Arr::get($url, 'provider'); |
| 208 |
|
| 209 |
if ($provider == 'giphy') { |
| 210 |
return null; |
| 211 |
} |
| 212 |
|
| 213 |
$url = Arr::get($url, 'url'); |
| 214 |
} |
| 215 |
|
| 216 |
if (!$url) { |
| 217 |
return null; |
| 218 |
} |
| 219 |
|
| 220 |
$parsedUrl = wp_parse_url($url, PHP_URL_QUERY); |
| 221 |
|
| 222 |
if (!$parsedUrl) { |
| 223 |
return null; |
| 224 |
} |
| 225 |
|
| 226 |
// Parse the query string to get the media_key value |
| 227 |
parse_str($parsedUrl, $queryParams); |
| 228 |
|
| 229 |
$key = Arr::get($queryParams, 'media_key'); |
| 230 |
|
| 231 |
if (!$key) { |
| 232 |
return null; |
| 233 |
} |
| 234 |
|
| 235 |
return Media::where('media_key', $key)->first(); |
| 236 |
} |
| 237 |
|
| 238 |
/** |
| 239 |
* Get media items from multiple URLs. |
| 240 |
* |
| 241 |
* @param array $urls An array of URLs. |
| 242 |
* @return array An array of Media objects. |
| 243 |
*/ |
| 244 |
public static function getMediaItemsFromUrl($urls) |
| 245 |
{ |
| 246 |
$mediaItems = []; |
| 247 |
|
| 248 |
foreach ($urls as $url) { |
| 249 |
$media = self::getMediaFromUrl($url); |
| 250 |
|
| 251 |
if ($media) { |
| 252 |
$mediaItems[] = $media; |
| 253 |
} |
| 254 |
} |
| 255 |
|
| 256 |
return $mediaItems; |
| 257 |
} |
| 258 |
|
| 259 |
/** |
| 260 |
* Get general settings for the community. |
| 261 |
* |
| 262 |
* @param bool $cached Whether to use cached settings. |
| 263 |
* @return array The general settings. |
| 264 |
*/ |
| 265 |
public static function generalSettings($cached = true) |
| 266 |
{ |
| 267 |
static $settings = null; |
| 268 |
|
| 269 |
if ($cached && $settings) { |
| 270 |
return $settings; |
| 271 |
} |
| 272 |
|
| 273 |
$settings = get_option('fluent_community_settings', []); |
| 274 |
|
| 275 |
$defaults = [ |
| 276 |
'site_title' => get_bloginfo('name'), |
| 277 |
'slug' => 'portal', |
| 278 |
'logo' => '', |
| 279 |
'white_logo' => '', |
| 280 |
'featured_image' => '', |
| 281 |
'access' => [ |
| 282 |
'acess_level' => 'logged_in', // logged_in, public, role_based |
| 283 |
'access_roles' => [] |
| 284 |
], |
| 285 |
'disable_global_posts' => 'yes', |
| 286 |
'auth_content' => 'Please login first to access this page', |
| 287 |
'auth_redirect' => '', |
| 288 |
'restricted_role_content' => 'Sorry, you can not access to this page. Only authorized users can access this page.', |
| 289 |
'auth_url' => '' |
| 290 |
]; |
| 291 |
|
| 292 |
$settings = wp_parse_args($settings, $defaults); |
| 293 |
|
| 294 |
if (defined('FLUENT_COMMUNITY_PORTAL_SLUG')) { |
| 295 |
$settings['slug'] = \FLUENT_COMMUNITY_PORTAL_SLUG; |
| 296 |
$settings['is_slug_defined'] = true; |
| 297 |
} else { |
| 298 |
unset($settings['is_slug_defined']); |
| 299 |
} |
| 300 |
|
| 301 |
return $settings; |
| 302 |
} |
| 303 |
|
| 304 |
public static function hasGlobalPost() |
| 305 |
{ |
| 306 |
$settings = self::generalSettings(); |
| 307 |
return Arr::get($settings, 'disable_global_posts', '') != 'yes'; |
| 308 |
} |
| 309 |
|
| 310 |
/** |
| 311 |
* Check if a user can access the portal. |
| 312 |
* |
| 313 |
* @param int|null $userId The user ID. If null, uses the current user. |
| 314 |
* @return bool True if the user can access the portal, false otherwise. |
| 315 |
*/ |
| 316 |
public static function canAccessPortal($userId = null) |
| 317 |
{ |
| 318 |
$settings = self::generalSettings(); |
| 319 |
|
| 320 |
$accessLevel = Arr::get($settings, 'access.acess_level'); |
| 321 |
|
| 322 |
if ($accessLevel == 'public') { |
| 323 |
return true; |
| 324 |
} |
| 325 |
|
| 326 |
if (!$userId) { |
| 327 |
$userId = get_current_user_id(); |
| 328 |
} |
| 329 |
|
| 330 |
if (!$userId) { |
| 331 |
return false; |
| 332 |
} |
| 333 |
|
| 334 |
if ($accessLevel == 'logged_in') { |
| 335 |
return true; |
| 336 |
} |
| 337 |
|
| 338 |
if (user_can($userId, 'edit_pages')) { |
| 339 |
return true; |
| 340 |
} |
| 341 |
|
| 342 |
$roles = Arr::get($settings, 'access.access_roles', []); |
| 343 |
|
| 344 |
$user = get_user_by('ID', $userId); |
| 345 |
|
| 346 |
if (!$user) { |
| 347 |
return false; |
| 348 |
} |
| 349 |
|
| 350 |
$result = !!array_intersect(array_values($user->roles), $roles); |
| 351 |
|
| 352 |
if (!$result) { |
| 353 |
return false; |
| 354 |
} |
| 355 |
|
| 356 |
$xProfile = Helper::getCurrentProfile(); |
| 357 |
|
| 358 |
return $xProfile && $xProfile->status == 'active'; |
| 359 |
} |
| 360 |
|
| 361 |
/** |
| 362 |
* Get the portal route paths. |
| 363 |
* |
| 364 |
* @return array An array of portal route paths. |
| 365 |
*/ |
| 366 |
public static function portalRoutePaths() |
| 367 |
{ |
| 368 |
return apply_filters('fluent_community/app_route_paths', [ |
| 369 |
'portal_home', |
| 370 |
'community', |
| 371 |
'admin', |
| 372 |
'members', |
| 373 |
'u', |
| 374 |
'discussions', |
| 375 |
'notifications', |
| 376 |
'bookmarks', |
| 377 |
'courses' |
| 378 |
]); |
| 379 |
} |
| 380 |
|
| 381 |
/** |
| 382 |
* Get the current user's profile. |
| 383 |
* |
| 384 |
* @param bool $cached Whether to use cached profile. |
| 385 |
* @return XProfile|null The user's profile or null if not found. |
| 386 |
*/ |
| 387 |
public static function getCurrentProfile($cached = true) |
| 388 |
{ |
| 389 |
$userId = get_current_user_id(); |
| 390 |
if (!$userId) { |
| 391 |
return null; |
| 392 |
} |
| 393 |
|
| 394 |
static $profile; |
| 395 |
|
| 396 |
if ($profile && $cached) { |
| 397 |
return $profile; |
| 398 |
} |
| 399 |
|
| 400 |
if (!$userId) { |
| 401 |
return null; |
| 402 |
} |
| 403 |
|
| 404 |
$profile = XProfile::where('user_id', $userId)->first(); |
| 405 |
|
| 406 |
return $profile; |
| 407 |
} |
| 408 |
|
| 409 |
/** |
| 410 |
* Get the current user Model. |
| 411 |
* |
| 412 |
* @param bool $cached Whether to use cached user. |
| 413 |
* @return User|false The User model or false if not found. |
| 414 |
*/ |
| 415 |
public static function getCurrentUser($cached = true) |
| 416 |
{ |
| 417 |
$userId = get_current_user_id(); |
| 418 |
if (!$userId) { |
| 419 |
return false; |
| 420 |
} |
| 421 |
|
| 422 |
static $user; |
| 423 |
if ($user && $cached) { |
| 424 |
return $user; |
| 425 |
} |
| 426 |
|
| 427 |
$user = User::find($userId); |
| 428 |
|
| 429 |
return $user; |
| 430 |
} |
| 431 |
|
| 432 |
/** |
| 433 |
* Get the route paths for the community. |
| 434 |
* |
| 435 |
* @return array An array of route paths. |
| 436 |
*/ |
| 437 |
private static function getRoutePaths() |
| 438 |
{ |
| 439 |
return [ |
| 440 |
'dashboard' => '/dashboard', |
| 441 |
'all_feeds' => '/', |
| 442 |
'single_feed' => '/post/:feed_slug', |
| 443 |
'space_feeds' => '/space/:space/home', |
| 444 |
'space_feed' => '/space/:space/post/:feed_slug', |
| 445 |
'space_members' => '/space/:space/members', |
| 446 |
'spaces' => '/discover/spaces', |
| 447 |
'settings' => '/admin/settings', |
| 448 |
'admin_moderators' => '/admin/settings/moderators', |
| 449 |
'all_members' => '/members', |
| 450 |
'user_profile' => '/u/:username/', |
| 451 |
'user_communities' => '/u/:username/spaces', |
| 452 |
'update_profile' => '/u/:username/update', |
| 453 |
'discussions' => '/discussions', |
| 454 |
'create_topic' => '/discussions/create-topic', |
| 455 |
'topic' => '/discussions/topic/:slug', |
| 456 |
'notifications' => '/notifications', |
| 457 |
'bookmarks' => '/bookmarks', |
| 458 |
'courses' => '/courses', |
| 459 |
'view_course' => '/courses/view/:course_id/lessons', |
| 460 |
'view_lesson' => '/courses/view/:course_id/lessons/:lesson_slug/view', |
| 461 |
'manage_courses' => '/admin/manage-courses', |
| 462 |
'edit_lessons' => '/admin/manage-courses/edit/:course_id/lessons', |
| 463 |
'course_students' => '/admin/manage-courses/edit/:course_id/students', |
| 464 |
'course_overview' => '/admin/manage-courses/edit/:course_id/overview', |
| 465 |
'manage_leaderboard' => '/admin/manage-leaderboard', |
| 466 |
]; |
| 467 |
} |
| 468 |
|
| 469 |
/** |
| 470 |
* Get the URL for a JavaScript route. |
| 471 |
* |
| 472 |
* @param array $route The route information. |
| 473 |
* @return string The URL for the route. |
| 474 |
*/ |
| 475 |
public static function getUrlByJsRoute($route = []) |
| 476 |
{ |
| 477 |
$routePaths = self::getRoutePaths(); |
| 478 |
|
| 479 |
$routeName = Arr::get($route, 'name', ''); |
| 480 |
|
| 481 |
if (!$routeName || !isset($routePaths[$routeName])) { |
| 482 |
return self::baseUrl(); |
| 483 |
} |
| 484 |
|
| 485 |
$path = $routePaths[$routeName]; |
| 486 |
|
| 487 |
$params = (array)Arr::get($route, 'params', []); |
| 488 |
|
| 489 |
if (!$params) { |
| 490 |
return self::baseUrl($path); |
| 491 |
} |
| 492 |
|
| 493 |
$replaces = []; |
| 494 |
|
| 495 |
foreach ($params as $paramKey => $paramValue) { |
| 496 |
$replaces[':' . $paramKey] = $paramValue; |
| 497 |
} |
| 498 |
|
| 499 |
$path = str_replace(array_keys($replaces), array_values($replaces), $path); |
| 500 |
|
| 501 |
return self::baseUrl($path); |
| 502 |
|
| 503 |
} |
| 504 |
|
| 505 |
/** |
| 506 |
* Get the route name from a request path. |
| 507 |
* |
| 508 |
* @param string $path The request path. |
| 509 |
* @return string|false The route name or false if not found. |
| 510 |
*/ |
| 511 |
public static function getRouteNameByRequestPath($path) |
| 512 |
{ |
| 513 |
$path = '//' . $path; |
| 514 |
|
| 515 |
if (strpos($path, '/u/')) { |
| 516 |
return 'user_profile'; |
| 517 |
} |
| 518 |
|
| 519 |
if (strpos($path, '/post/')) { |
| 520 |
return 'feed_view'; |
| 521 |
} |
| 522 |
|
| 523 |
if (strpos($path, '/lessons/')) { |
| 524 |
return 'lesson_view'; |
| 525 |
} |
| 526 |
|
| 527 |
if (strpos($path, '/course/')) { |
| 528 |
return 'course_view'; |
| 529 |
} |
| 530 |
|
| 531 |
if (strpos($path, '/space/') && !strpos($path, '/discover/spaces')) { |
| 532 |
return 'community_view'; |
| 533 |
} |
| 534 |
|
| 535 |
if (strpos($path, '/admin')) { |
| 536 |
return 'admin'; |
| 537 |
} |
| 538 |
|
| 539 |
return false; |
| 540 |
} |
| 541 |
|
| 542 |
/** |
| 543 |
* Get a human-readable excerpt from content. |
| 544 |
* |
| 545 |
* @param string $content The content to extract from. |
| 546 |
* @param int $length The maximum length of the excerpt. |
| 547 |
* @return string The human-readable excerpt. |
| 548 |
*/ |
| 549 |
public static function getHumanExcerpt($content, $length = 100) |
| 550 |
{ |
| 551 |
if ($content) { |
| 552 |
$patterns = [ |
| 553 |
'/^#{1,6}\s+/m' => '', |
| 554 |
// Bold and Italic: remove '*' and '_' symbols |
| 555 |
'/(\*\*|__)(.*?)\1/' => '$2', |
| 556 |
'/(\*|_)(.*?)\1/' => '$2', |
| 557 |
// Code blocks: remove triple backticks |
| 558 |
'/^```\s*\w*\s*\n([\s\S]*?)\n```\s*$/m' => '$1', |
| 559 |
// Inline code: remove single backticks |
| 560 |
'/`([^`]+)`/' => '$1', |
| 561 |
// Blockquotes: remove '>' symbol |
| 562 |
'/^\s*>\s?/m' => '', |
| 563 |
// Horizontal rules: replace with empty line |
| 564 |
'/^\s*([-*_])\1{2,}\s*$/m' => "\n", |
| 565 |
// Links: keep only the link text |
| 566 |
'/\[([^\]]+)\]\([^\)]+\)/' => '$1', |
| 567 |
// Images: keep only the alt text |
| 568 |
'/!\[([^\]]+)\]\([^\)]+\)/' => '$1', |
| 569 |
// Strikethrough: remove '~~' symbols |
| 570 |
'/~~(.*?)~~/' => '$1', |
| 571 |
// Task lists: remove checkbox syntax |
| 572 |
'/^\s*[-*+]\s+\[[ xX]\]\s+/m' => '', |
| 573 |
]; |
| 574 |
|
| 575 |
$content = preg_replace(array_keys($patterns), array_values($patterns), $content); |
| 576 |
|
| 577 |
// remove all tags |
| 578 |
$content = wp_strip_all_tags($content); |
| 579 |
// remove new lines and tabs |
| 580 |
$content = str_replace(["\r", "\n", "\t"], ' ', $content); |
| 581 |
// remove multiple spaces |
| 582 |
$content = preg_replace('/\s+/', ' ', $content); |
| 583 |
|
| 584 |
// trim |
| 585 |
$content = trim($content); |
| 586 |
} |
| 587 |
|
| 588 |
if (!$content) { |
| 589 |
return ''; |
| 590 |
} |
| 591 |
|
| 592 |
if (mb_strlen($content) <= $length) { |
| 593 |
return $content; |
| 594 |
} |
| 595 |
|
| 596 |
// return the first $length chars of the content with ... at the end |
| 597 |
return mb_substr($content, 0, $length) . '...'; |
| 598 |
} |
| 599 |
|
| 600 |
/** |
| 601 |
* Check if the portal is publicly accessible. |
| 602 |
* |
| 603 |
* @return bool True if publicly accessible, false otherwise. |
| 604 |
*/ |
| 605 |
public static function isPublicAccessible() |
| 606 |
{ |
| 607 |
$settings = self::generalSettings(); |
| 608 |
|
| 609 |
return Arr::get($settings, 'access.acess_level') == 'public'; |
| 610 |
} |
| 611 |
|
| 612 |
/** |
| 613 |
* Get media by provider. |
| 614 |
* |
| 615 |
* @param array $images The array of images. |
| 616 |
* @param string $provider The provider to filter by. |
| 617 |
* @return array The filtered array of images. |
| 618 |
*/ |
| 619 |
public static function getMediaByProvider($images, $provider = 'uploader') |
| 620 |
{ |
| 621 |
if (is_array($images)) { |
| 622 |
return array_filter($images, function ($image) use ($provider) { |
| 623 |
if (is_array($image)) { |
| 624 |
if (isset($image['provider'])) { |
| 625 |
return Arr::get($image, 'provider') == $provider; |
| 626 |
} |
| 627 |
|
| 628 |
return $provider === 'uploader'; // for existing images when no provider was set. |
| 629 |
} |
| 630 |
}); |
| 631 |
} |
| 632 |
|
| 633 |
return []; |
| 634 |
} |
| 635 |
|
| 636 |
/** |
| 637 |
* Get the community menu groups. |
| 638 |
* |
| 639 |
* @param User|null $user The user to get menu groups for. |
| 640 |
* @return array The community menu groups. |
| 641 |
*/ |
| 642 |
public static function getCommunityMenuGroups($user = null) |
| 643 |
{ |
| 644 |
if (!$user) { |
| 645 |
$user = self::getCurrentUser(); |
| 646 |
} |
| 647 |
|
| 648 |
$communityGroups = self::getAllCommunityGroups($user); |
| 649 |
|
| 650 |
if ($communityGroups->isEmpty()) { |
| 651 |
return []; |
| 652 |
} |
| 653 |
|
| 654 |
$isMod = $user && $user->isCommunityModerator(); |
| 655 |
|
| 656 |
$formattedGroups = []; |
| 657 |
|
| 658 |
foreach ($communityGroups as $communityGroup) { |
| 659 |
$spaces = $communityGroup->spaces; |
| 660 |
$validSpaces = []; |
| 661 |
$isShowAll = Arr::get($communityGroup->settings, 'always_show_spaces') === 'yes'; |
| 662 |
|
| 663 |
foreach ($spaces as $space) { |
| 664 |
if ($isMod) { |
| 665 |
$validSpaces[] = self::transformSpaceToLink($space); |
| 666 |
continue; |
| 667 |
} |
| 668 |
|
| 669 |
if ($space->privacy === 'secret') { |
| 670 |
if (!$user || !$space->getMembership($user->ID)) { |
| 671 |
continue; |
| 672 |
} |
| 673 |
$validSpaces[] = self::transformSpaceToLink($space); |
| 674 |
continue; |
| 675 |
} |
| 676 |
|
| 677 |
if ($isShowAll || $space->privacy = 'public') { |
| 678 |
$validSpace = self::transformSpaceToLink($space); |
| 679 |
|
| 680 |
if ($space->privacy == 'private') { |
| 681 |
if (!$user || !$space->getMembership($user->ID)) { |
| 682 |
$validSpace['show_lock'] = true; |
| 683 |
} |
| 684 |
} |
| 685 |
|
| 686 |
$validSpaces[] = $validSpace; |
| 687 |
continue; |
| 688 |
} |
| 689 |
|
| 690 |
if (!$user || $space->getMembership($user->ID)) { |
| 691 |
continue; |
| 692 |
} |
| 693 |
|
| 694 |
$validSpaces[] = self::transformSpaceToLink($space); |
| 695 |
} |
| 696 |
|
| 697 |
if (!$validSpaces && !$isMod) { |
| 698 |
continue; |
| 699 |
} |
| 700 |
|
| 701 |
$formattedGroups[] = [ |
| 702 |
'id' => $communityGroup->id, |
| 703 |
'title' => $communityGroup->title, |
| 704 |
'slug' => $communityGroup->slug, |
| 705 |
'logo' => $communityGroup->logo, |
| 706 |
'children' => $validSpaces |
| 707 |
]; |
| 708 |
} |
| 709 |
|
| 710 |
return $formattedGroups; |
| 711 |
} |
| 712 |
|
| 713 |
/** |
| 714 |
* Transform a space to a link array. |
| 715 |
* |
| 716 |
* @param Space $space The space to transform. |
| 717 |
* @return array The transformed space link array. |
| 718 |
*/ |
| 719 |
private static function transformSpaceToLink($space) |
| 720 |
{ |
| 721 |
|
| 722 |
$logo = $space->logo; |
| 723 |
|
| 724 |
$title = $space->title; |
| 725 |
|
| 726 |
if ($space->status == 'draft') { |
| 727 |
$title = $title . ' ' . __('(Draft)', 'fluent-community'); |
| 728 |
} |
| 729 |
|
| 730 |
return [ |
| 731 |
'title' => $title, |
| 732 |
'icon_image' => $logo, |
| 733 |
'shape_svg' => !$logo ? Arr::get($space->settings, 'shape_svg', '') : '', |
| 734 |
'emoji' => !$logo ? Arr::get($space->settings, 'emoji', '') : '', |
| 735 |
'permalink' => $space->getPermalink(), |
| 736 |
'link_classes' => 'space_menu_item route_url fcom_space_id_' . $space->id . ' fcom_space_' . $space->slug |
| 737 |
]; |
| 738 |
} |
| 739 |
|
| 740 |
public static function isAlreadyOnboarded() |
| 741 |
{ |
| 742 |
$communitySettings = get_option('fluent_community_settings', []); |
| 743 |
|
| 744 |
return !empty($communitySettings); |
| 745 |
} |
| 746 |
|
| 747 |
/** |
| 748 |
* Get all community groups. |
| 749 |
* |
| 750 |
* @param User $user The user to get groups for. |
| 751 |
* @param bool $willCreate Whether to create a default group if none exist. |
| 752 |
* @return \FluentCommunity\Framework\Support\Collection Collection of Groups |
| 753 |
*/ |
| 754 |
public static function getAllCommunityGroups($user, $willCreate = true) |
| 755 |
{ |
| 756 |
$isModerator = $user && $user->isCommunityModerator(); |
| 757 |
|
| 758 |
$communityGroups = SpaceGroup::query()->orderBy('serial', 'ASC') |
| 759 |
->with([ |
| 760 |
'spaces' => function ($query) use ($isModerator) { |
| 761 |
if ($isModerator) { |
| 762 |
$query->orderBy('serial', 'ASC'); |
| 763 |
} else { |
| 764 |
$query->where('status', 'published') |
| 765 |
->orderBy('serial', 'ASC'); |
| 766 |
} |
| 767 |
} |
| 768 |
]) |
| 769 |
->get(); |
| 770 |
|
| 771 |
if ($communityGroups->isEmpty() && $willCreate) { |
| 772 |
$createdSpace = (new ActivationHandler(App::make()))->maybeCreateDefaultSpaceGroup(); |
| 773 |
|
| 774 |
if ($createdSpace) { |
| 775 |
Space::where('type', 'community')->update([ |
| 776 |
'parent_id' => $createdSpace->id |
| 777 |
]); |
| 778 |
} |
| 779 |
|
| 780 |
return self::getAllCommunityGroups($user, false); |
| 781 |
} |
| 782 |
|
| 783 |
return $communityGroups; |
| 784 |
} |
| 785 |
|
| 786 |
/** |
| 787 |
* Check if a feature is enabled. |
| 788 |
* |
| 789 |
* @param string $feature The feature to check. |
| 790 |
* @return bool True if the feature is enabled, false otherwise. |
| 791 |
*/ |
| 792 |
public static function isFeatureEnabled($feature) |
| 793 |
{ |
| 794 |
$features = Utility::getFeaturesConfig(); |
| 795 |
|
| 796 |
return isset($features[$feature]) && $features[$feature] === 'yes'; |
| 797 |
} |
| 798 |
|
| 799 |
/** |
| 800 |
* Get the menu items group. |
| 801 |
* |
| 802 |
* @param string $context The context for getting menu items. |
| 803 |
* @return array The menu items group. |
| 804 |
*/ |
| 805 |
public static function getMenuItemsGroup($context = 'view') |
| 806 |
{ |
| 807 |
static $menuGroups; |
| 808 |
|
| 809 |
if ($menuGroups && $context === 'view') { |
| 810 |
return $menuGroups; |
| 811 |
} |
| 812 |
|
| 813 |
$menuGroups = Utility::getOption('fluent_community_menu_groups', []); |
| 814 |
|
| 815 |
$defaultMainMenuItems = [ |
| 816 |
'all_feeds' => [ |
| 817 |
'slug' => 'all_feeds', |
| 818 |
'title' => __('Feed', 'fluent-community'), |
| 819 |
'is_system' => 'yes', |
| 820 |
'is_locked' => 'yes', |
| 821 |
'enabled' => 'yes', |
| 822 |
'permalink' => self::baseUrl('/'), |
| 823 |
'link_classes' => 'fcom_dashboard route_url', |
| 824 |
'shape_svg' => '<svg width="20" height="18" viewBox="0 0 20 18" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M10 13.166H10.0075H10Z" fill="currentColor"/><path d="M10 13.166H10.0075" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M16.6666 6.08301V10.2497C16.6666 13.3924 16.6666 14.9637 15.6903 15.94C14.714 16.9163 13.1426 16.9163 9.99992 16.9163C6.85722 16.9163 5.28587 16.9163 4.30956 15.94C3.33325 14.9637 3.33325 13.3924 3.33325 10.2497V6.08301" stroke="currentColor" stroke-width="1.5"/><path d="M18.3333 7.74967L14.714 4.27925C12.4918 2.14842 11.3807 1.08301 9.99996 1.08301C8.61925 1.08301 7.50814 2.14842 5.28592 4.27924L1.66663 7.74967" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>' |
| 825 |
], |
| 826 |
'spaces' => [ |
| 827 |
'slug' => 'spaces', |
| 828 |
'title' => __('Spaces', 'fluent-community'), |
| 829 |
'is_system' => 'yes', |
| 830 |
'is_locked' => 'yes', |
| 831 |
'enabled' => 'yes', |
| 832 |
'permalink' => self::baseUrl('discover/spaces'), |
| 833 |
'link_classes' => 'fcom_spaces route_url', |
| 834 |
'shape_svg' => '<svg version="1.1" viewBox="0 0 128 128" xml:space="preserve"><g><path d="M64,42c-13.2,0-24,10.8-24,24s10.8,24,24,24s24-10.8,24-24S77.2,42,64,42z M64,82c-8.8,0-16-7.2-16-16s7.2-16,16-16 s16,7.2,16,16S72.8,82,64,82z"/><path d="M64,100.8c-14.9,0-29.2,6.2-39.4,17.1l-2.7,2.9l5.8,5.5l2.7-2.9c8.8-9.4,20.7-14.6,33.6-14.6s24.8,5.2,33.6,14.6l2.7,2.9 l5.8-5.5l-2.7-2.9C93.2,107.1,78.9,100.8,64,100.8z"/><path d="M97,47.9v8c9.4,0,18.1,3.8,24.6,10.7l5.8-5.5C119.6,52.7,108.5,47.9,97,47.9z"/><path d="M116.1,20c0-10.5-8.6-19.1-19.1-19.1S77.9,9.5,77.9,20S86.5,39.1,97,39.1S116.1,30.5,116.1,20z M85.9,20 c0-6.1,5-11.1,11.1-11.1s11.1,5,11.1,11.1s-5,11.1-11.1,11.1S85.9,26.1,85.9,20z"/><path d="M31,47.9c-11.5,0-22.6,4.8-30.4,13.2l5.8,5.5c6.4-6.9,15.2-10.7,24.6-10.7V47.9z"/><path d="M50.1,20C50.1,9.5,41.5,0.9,31,0.9S11.9,9.5,11.9,20S20.5,39.1,31,39.1S50.1,30.5,50.1,20z M31,31.1 c-6.1,0-11.1-5-11.1-11.1S24.9,8.9,31,8.9s11.1,5,11.1,11.1S37.1,31.1,31,31.1z"/></g></svg>' |
| 835 |
], |
| 836 |
'all_courses' => [ |
| 837 |
'slug' => 'all_courses', |
| 838 |
'title' => 'Courses', |
| 839 |
'link_classes' => 'fcom_courses route_url', |
| 840 |
'is_system' => 'yes', |
| 841 |
'is_locked' => 'yes', |
| 842 |
'enabled' => 'yes', |
| 843 |
'is_unavailable' => self::isFeatureEnabled('course_module') ? 'no' : 'yes', |
| 844 |
'permalink' => self::baseUrl('courses'), |
| 845 |
'shape_svg' => '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10.734 5.84746L14.7114 6.9072M9.88139 9.01146L11.8701 9.54132M9.98031 14.9723L10.7758 15.1843C13.0258 15.7838 14.1508 16.0835 15.037 15.5747C15.9233 15.0659 16.2247 13.9473 16.8276 11.71L17.6802 8.54599C18.2831 6.3087 18.5845 5.19006 18.0728 4.30879C17.5611 3.42752 16.4362 3.12778 14.1862 2.52831L13.3907 2.31636C11.1407 1.71688 10.0157 1.41714 9.12948 1.92594C8.24322 2.43474 7.94178 3.55338 7.3389 5.79067L6.4863 8.95466C5.88342 11.1919 5.58198 12.3106 6.09367 13.1919C6.60536 14.0731 7.73034 14.3729 9.98031 14.9723Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M9.99996 17.4559L9.20634 17.672C6.96165 18.2832 5.83931 18.5889 4.95512 18.0701C4.07093 17.5513 3.7702 16.4107 3.16874 14.1295L2.31814 10.9035C1.71668 8.62232 1.41595 7.48174 1.92643 6.58318C2.36802 5.80591 3.33329 5.83421 4.58329 5.83411" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>' |
| 846 |
], |
| 847 |
'all_members' => [ |
| 848 |
'slug' => 'all_members', |
| 849 |
'title' => __('Members', 'fluent-community'), |
| 850 |
'is_system' => 'yes', |
| 851 |
'is_locked' => 'yes', |
| 852 |
'enabled' => 'yes', |
| 853 |
'permalink' => self::baseUrl('members'), |
| 854 |
'link_classes' => 'fcom_all_members route_url', |
| 855 |
'shape_svg' => '<svg width="20" height="16" viewBox="0 0 20 16" fill="none"><path d="M17.3116 13C17.936 13 18.4327 12.6071 18.8786 12.0576C19.7915 10.9329 18.2927 10.034 17.721 9.59383C17.1399 9.14635 16.4911 8.89285 15.8332 8.83333M14.9999 7.16667C16.1505 7.16667 17.0832 6.23393 17.0832 5.08333C17.0832 3.93274 16.1505 3 14.9999 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M2.68822 13C2.0638 13 1.56714 12.6071 1.12121 12.0576C0.208326 10.9329 1.70714 10.034 2.27879 9.59383C2.8599 9.14635 3.50874 8.89285 4.16659 8.83333M4.58325 7.16667C3.43266 7.16667 2.49992 6.23393 2.49992 5.08333C2.49992 3.93274 3.43266 3 4.58325 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M6.73642 10.592C5.88494 11.1185 3.65241 12.1936 5.01217 13.5389C5.6764 14.196 6.41619 14.666 7.34627 14.666H12.6536C13.5837 14.666 14.3234 14.196 14.9877 13.5389C16.3474 12.1936 14.1149 11.1185 13.2634 10.592C11.2667 9.35735 8.73313 9.35735 6.73642 10.592Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M12.9166 4.24967C12.9166 5.86051 11.6107 7.16634 9.99992 7.16634C8.38909 7.16634 7.08325 5.86051 7.08325 4.24967C7.08325 2.63884 8.38909 1.33301 9.99992 1.33301C11.6107 1.33301 12.9166 2.63884 12.9166 4.24967Z" stroke="currentColor" stroke-width="1.5"/></svg>', |
| 856 |
], |
| 857 |
'leaderboard' => [ |
| 858 |
'slug' => 'leaderboard', |
| 859 |
'is_system' => 'yes', |
| 860 |
'is_locked' => 'yes', |
| 861 |
'enabled' => 'yes', |
| 862 |
'is_unavailable' => self::isFeatureEnabled('leader_board_module') ? 'no' : 'yes', |
| 863 |
'title' => __('Leaderboard', 'fluent-community'), |
| 864 |
'link_classes' => 'fcom_leaderboards route_url', |
| 865 |
'permalink' => self::baseUrl('leaderboards'), |
| 866 |
'shape_svg' => '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="M160-200h160v-320H160v320Zm240 0h160v-560H400v560Zm240 0h160v-240H640v240ZM80-120v-480h240v-240h320v320h240v400H80Z"/></svg>' |
| 867 |
] |
| 868 |
]; |
| 869 |
|
| 870 |
$mainItems = Arr::get($menuGroups, 'mainMenuItems', []); |
| 871 |
|
| 872 |
if ($mainItems && is_array($mainItems)) { |
| 873 |
if (isset($mainItems['all_communities'])) { |
| 874 |
$mainItems['spaces'] = $defaultMainMenuItems['spaces']; |
| 875 |
unset($mainItems['all_communities']); |
| 876 |
} |
| 877 |
|
| 878 |
foreach ($mainItems as $index => &$item) { |
| 879 |
if (empty($item['slug'])) { |
| 880 |
unset($mainItems[$index]); |
| 881 |
continue; |
| 882 |
} |
| 883 |
$defaultItem = Arr::get($defaultMainMenuItems, $item['slug'], []); |
| 884 |
if ($defaultItem) { |
| 885 |
$preservedKeys = ['is_system', 'is_locked', 'is_unavailable', 'slug', 'shape_svg']; |
| 886 |
foreach ($preservedKeys as $key) { |
| 887 |
if (isset($defaultItem[$key])) { |
| 888 |
$item[$key] = Arr::get($defaultItem, $key); |
| 889 |
} |
| 890 |
} |
| 891 |
if (Arr::get($defaultItem, 'is_system') === 'yes') { |
| 892 |
$item['permalink'] = $defaultItem['permalink']; |
| 893 |
$item['emoji'] = ''; |
| 894 |
$item['icon_image'] = ''; |
| 895 |
$item['link_classes'] = $defaultItem['link_classes']; |
| 896 |
} |
| 897 |
} |
| 898 |
} |
| 899 |
} else { |
| 900 |
$mainItems = $defaultMainMenuItems; |
| 901 |
} |
| 902 |
|
| 903 |
$defaultProfileDropDownItems = [ |
| 904 |
'my_spaces' => [ |
| 905 |
'slug' => 'my_spaces', |
| 906 |
'title' => __('My Spaces', 'fluent-community'), |
| 907 |
'is_system' => 'yes', |
| 908 |
'is_locked' => 'yes', |
| 909 |
'enabled' => 'yes', |
| 910 |
'permalink' => '#{{user_url}}/spaces', |
| 911 |
'shape_svg' => '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.5 16.5C0.5 14.9087 1.13214 13.3826 2.25736 12.2574C3.38258 11.1321 4.9087 10.5 6.5 10.5C8.0913 10.5 9.61742 11.1321 10.7426 12.2574C11.8679 13.3826 12.5 14.9087 12.5 16.5H11C11 15.3065 10.5259 14.1619 9.68198 13.318C8.83807 12.4741 7.69347 12 6.5 12C5.30653 12 4.16193 12.4741 3.31802 13.318C2.47411 14.1619 2 15.3065 2 16.5H0.5ZM6.5 9.75C4.01375 9.75 2 7.73625 2 5.25C2 2.76375 4.01375 0.75 6.5 0.75C8.98625 0.75 11 2.76375 11 5.25C11 7.73625 8.98625 9.75 6.5 9.75ZM6.5 8.25C8.1575 8.25 9.5 6.9075 9.5 5.25C9.5 3.5925 8.1575 2.25 6.5 2.25C4.8425 2.25 3.5 3.5925 3.5 5.25C3.5 6.9075 4.8425 8.25 6.5 8.25ZM12.713 11.0273C13.767 11.5019 14.6615 12.2709 15.2889 13.2418C15.9164 14.2126 16.2501 15.344 16.25 16.5H14.75C14.7502 15.633 14.4999 14.7844 14.0293 14.0562C13.5587 13.328 12.8878 12.7512 12.0972 12.3953L12.7123 11.0273H12.713ZM12.197 2.55975C12.9526 2.87122 13.5987 3.40015 14.0533 4.07942C14.5078 4.75869 14.7503 5.55768 14.75 6.375C14.7503 7.40425 14.3658 8.39642 13.6719 9.15662C12.978 9.91682 12.025 10.3901 11 10.4835V8.97375C11.5557 8.89416 12.0713 8.63851 12.471 8.24434C12.8707 7.85017 13.1335 7.33824 13.2209 6.7837C13.3082 6.22916 13.2155 5.66122 12.9563 5.16327C12.6971 4.66531 12.2851 4.26356 11.7808 4.017L12.197 2.55975Z" fill="currentColor"/></svg>' |
| 912 |
], |
| 913 |
'bookmarks' => [ |
| 914 |
'slug' => 'bookmarks', |
| 915 |
'title' => __('Bookmarks', 'fluent-community'), |
| 916 |
'is_system' => 'yes', |
| 917 |
'is_locked' => 'yes', |
| 918 |
'enabled' => 'yes', |
| 919 |
'permalink' => self::baseUrl('bookmarks'), |
| 920 |
'shape_svg' => '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.75 0.5H11.25C11.4489 0.5 11.6397 0.579018 11.7803 0.71967C11.921 0.860322 12 1.05109 12 1.25V15.6073C12.0001 15.6743 11.9822 15.7402 11.9482 15.7979C11.9142 15.8557 11.8653 15.9033 11.8066 15.9358C11.7479 15.9683 11.6816 15.9844 11.6146 15.9826C11.5476 15.9807 11.4823 15.9609 11.4255 15.9252L6 12.5225L0.5745 15.9245C0.517776 15.9601 0.452541 15.9799 0.385576 15.9818C0.318612 15.9837 0.252365 15.9676 0.193721 15.9352C0.135078 15.9029 0.0861801 15.8554 0.0521121 15.7977C0.0180441 15.74 4.98531e-05 15.6742 0 15.6073V1.25C0 1.05109 0.0790178 0.860322 0.21967 0.71967C0.360322 0.579018 0.551088 0.5 0.75 0.5ZM10.5 2H1.5V13.574L6 10.7533L10.5 13.574V2Z" fill="currentColor"/></svg>' |
| 921 |
], |
| 922 |
'logout' => [ |
| 923 |
'slug' => 'logout', |
| 924 |
'title' => __('Logout', 'fluent-community'), |
| 925 |
'is_system' => 'yes', |
| 926 |
'is_locked' => 'yes', |
| 927 |
'enabled' => 'yes', |
| 928 |
'permalink' => '#{{logout_url}}', |
| 929 |
'shape_svg' => '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.75 15.5C0.551088 15.5 0.360322 15.421 0.21967 15.2803C0.0790178 15.1397 0 14.9489 0 14.75V1.25C0 1.05109 0.0790178 0.860322 0.21967 0.71967C0.360322 0.579018 0.551088 0.5 0.75 0.5H11.25C11.4489 0.5 11.6397 0.579018 11.7803 0.71967C11.921 0.860322 12 1.05109 12 1.25V3.5H10.5V2H1.5V14H10.5V12.5H12V14.75C12 14.9489 11.921 15.1397 11.7803 15.2803C11.6397 15.421 11.4489 15.5 11.25 15.5H0.75ZM10.5 11V8.75H5.25V7.25H10.5V5L14.25 8L10.5 11Z" fill="currentColor"/></svg>' |
| 930 |
] |
| 931 |
]; |
| 932 |
|
| 933 |
$profileDropDownItems = Arr::get($menuGroups, 'profileDropdownItems', []); |
| 934 |
|
| 935 |
if ($profileDropDownItems && is_array($profileDropDownItems)) { |
| 936 |
|
| 937 |
unset($profileDropDownItems['profile']); |
| 938 |
|
| 939 |
foreach ($profileDropDownItems as $index => &$item) { |
| 940 |
if (empty($item['slug'])) { |
| 941 |
unset($profileDropDownItems[$index]); |
| 942 |
continue; |
| 943 |
} |
| 944 |
$defaultItem = Arr::get($defaultProfileDropDownItems, $item['slug'], []); |
| 945 |
if ($defaultItem) { |
| 946 |
$preservedKeys = ['is_system', 'is_locked', 'is_unavailable', 'slug', 'svg_icon']; |
| 947 |
foreach ($preservedKeys as $key) { |
| 948 |
if (isset($defaultItem[$key])) { |
| 949 |
$item[$key] = Arr::get($defaultItem, $key); |
| 950 |
} |
| 951 |
} |
| 952 |
if (Arr::get($defaultItem, 'is_system') === 'yes') { |
| 953 |
$item['permalink'] = $defaultItem['permalink']; |
| 954 |
if (empty($item['shape_svg'])) { |
| 955 |
$item['shape_svg'] = $defaultItem['shape_svg']; |
| 956 |
} |
| 957 |
} |
| 958 |
} |
| 959 |
} |
| 960 |
} else { |
| 961 |
$profileDropDownItems = $defaultProfileDropDownItems; |
| 962 |
} |
| 963 |
|
| 964 |
$beforeCommunityMenuItems = Arr::get($menuGroups, 'beforeCommunityMenuItems', []); |
| 965 |
$afterCommunityMenuGroups = Arr::get($menuGroups, 'afterCommunityLinkGroups', []); |
| 966 |
|
| 967 |
if (!is_array($beforeCommunityMenuItems)) { |
| 968 |
$beforeCommunityMenuItems = []; |
| 969 |
} |
| 970 |
|
| 971 |
if (!is_array($afterCommunityMenuGroups)) { |
| 972 |
$afterCommunityMenuGroups = []; |
| 973 |
} |
| 974 |
|
| 975 |
if ($context == 'view') { |
| 976 |
$mainItems = array_filter($mainItems, function ($item) { |
| 977 |
return Arr::get($item, 'enabled') === 'yes' && Arr::get($item, 'is_unavailable') !== 'yes'; |
| 978 |
}); |
| 979 |
|
| 980 |
$profileDropDownItems = array_filter($profileDropDownItems, function ($item) { |
| 981 |
return Arr::get($item, 'enabled') === 'yes' && Arr::get($item, 'is_unavailable') !== 'yes'; |
| 982 |
}); |
| 983 |
|
| 984 |
$beforeCommunityMenuItems = array_filter($beforeCommunityMenuItems, function ($item) { |
| 985 |
return Arr::get($item, 'enabled') === 'yes' && Arr::get($item, 'is_unavailable') !== 'yes'; |
| 986 |
}); |
| 987 |
|
| 988 |
$validGroups = []; |
| 989 |
foreach ($afterCommunityMenuGroups as $group) { |
| 990 |
if (empty($group['items']) || !is_array($group['items'])) { |
| 991 |
continue; |
| 992 |
} |
| 993 |
|
| 994 |
$group['items'] = array_filter($group['items'], function ($item) { |
| 995 |
return Arr::get($item, 'enabled') === 'yes' && Arr::get($item, 'is_unavailable') !== 'yes'; |
| 996 |
}); |
| 997 |
|
| 998 |
if ($group['items']) { |
| 999 |
$validGroups[] = $group; |
| 1000 |
} |
| 1001 |
} |
| 1002 |
|
| 1003 |
$afterCommunityMenuGroups = $validGroups; |
| 1004 |
} |
| 1005 |
|
| 1006 |
$menuGroups['mainMenuItems'] = $mainItems; |
| 1007 |
$menuGroups['profileDropdownItems'] = $profileDropDownItems; |
| 1008 |
$menuGroups['beforeCommunityMenuItems'] = $beforeCommunityMenuItems; |
| 1009 |
$menuGroups['afterCommunityLinkGroups'] = $afterCommunityMenuGroups; |
| 1010 |
|
| 1011 |
if ($context == 'view') { |
| 1012 |
$menuGroups = apply_filters('fluent_community/menu_groups', $menuGroups); |
| 1013 |
} |
| 1014 |
|
| 1015 |
return $menuGroups; |
| 1016 |
} |
| 1017 |
|
| 1018 |
/** |
| 1019 |
* Get the meta data for a space. |
| 1020 |
* |
| 1021 |
* @param int $spaceId The ID of the space. |
| 1022 |
* @param string $key The meta key. |
| 1023 |
* @param mixed $default The default value if the meta key is not found. |
| 1024 |
* @return mixed The meta value or the default value if not found. |
| 1025 |
*/ |
| 1026 |
public static function getSpaceMeta($spaceId, $key, $default = null) |
| 1027 |
{ |
| 1028 |
$meta = Meta::where('object_type', 'space') |
| 1029 |
->where('meta_key', $key) |
| 1030 |
->where('object_id', $spaceId) |
| 1031 |
->first(); |
| 1032 |
|
| 1033 |
if (!$meta) { |
| 1034 |
return $default; |
| 1035 |
} |
| 1036 |
|
| 1037 |
return $meta->value; |
| 1038 |
} |
| 1039 |
|
| 1040 |
/** |
| 1041 |
* Update the meta data for a space. |
| 1042 |
* |
| 1043 |
* @param int $spaceId The ID of the space. |
| 1044 |
* @param string $key The meta key. |
| 1045 |
* @param mixed $value The meta value. |
| 1046 |
* @return Meta The updated meta object. |
| 1047 |
*/ |
| 1048 |
public static function updateSpaceMeta($spaceId, $key, $value) |
| 1049 |
{ |
| 1050 |
$meta = Meta::where('object_type', 'space') |
| 1051 |
->where('meta_key', $key) |
| 1052 |
->where('object_id', $spaceId) |
| 1053 |
->first(); |
| 1054 |
|
| 1055 |
if ($meta) { |
| 1056 |
$meta->value = $value; |
| 1057 |
$meta->save(); |
| 1058 |
} else { |
| 1059 |
$meta = Meta::create([ |
| 1060 |
'object_type' => 'space', |
| 1061 |
'object_id' => $spaceId, |
| 1062 |
'meta_key' => $key, |
| 1063 |
'value' => $value |
| 1064 |
]); |
| 1065 |
} |
| 1066 |
|
| 1067 |
return $meta; |
| 1068 |
} |
| 1069 |
|
| 1070 |
|
| 1071 |
/** |
| 1072 |
* Encrypt or decrypt a value. |
| 1073 |
* |
| 1074 |
* @param string $value The value to encrypt or decrypt. |
| 1075 |
* @param string $type The type of operation ('e' for encrypt, 'd' for decrypt). |
| 1076 |
* @return string|false The encrypted or decrypted value or false if an error occurs. |
| 1077 |
*/ |
| 1078 |
public static function encryptDecrypt($value, $type = 'e') |
| 1079 |
{ |
| 1080 |
if (!$value) { |
| 1081 |
return $value; |
| 1082 |
} |
| 1083 |
|
| 1084 |
if (!extension_loaded('openssl')) { |
| 1085 |
return $value; |
| 1086 |
} |
| 1087 |
|
| 1088 |
if (defined('FLUENT_COM_ENCRYPT_SALT')) { |
| 1089 |
$salt = FLUENT_COM_ENCRYPT_SALT; |
| 1090 |
} else { |
| 1091 |
$salt = (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) ? LOGGED_IN_SALT : 'this-is-a-fallback-salt-but-not-secure'; |
| 1092 |
} |
| 1093 |
|
| 1094 |
if (defined('FLUENT_COM__ENCRYPT_KEY')) { |
| 1095 |
$key = FLUENT_COM__ENCRYPT_KEY; |
| 1096 |
} else { |
| 1097 |
$key = (defined('LOGGED_IN_KEY') && '' !== LOGGED_IN_KEY) ? LOGGED_IN_KEY : 'this-is-a-fallback-key-but-not-secure'; |
| 1098 |
} |
| 1099 |
|
| 1100 |
if ($type == 'e') { |
| 1101 |
$method = 'aes-256-ctr'; |
| 1102 |
$ivlen = openssl_cipher_iv_length($method); |
| 1103 |
$iv = openssl_random_pseudo_bytes($ivlen); |
| 1104 |
|
| 1105 |
$raw_value = openssl_encrypt($value . $salt, $method, $key, 0, $iv); |
| 1106 |
if (!$raw_value) { |
| 1107 |
return false; |
| 1108 |
} |
| 1109 |
|
| 1110 |
return base64_encode($iv . $raw_value); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode |
| 1111 |
} |
| 1112 |
|
| 1113 |
$raw_value = base64_decode($value, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode |
| 1114 |
|
| 1115 |
$method = 'aes-256-ctr'; |
| 1116 |
$ivlen = openssl_cipher_iv_length($method); |
| 1117 |
$iv = substr($raw_value, 0, $ivlen); |
| 1118 |
|
| 1119 |
$raw_value = substr($raw_value, $ivlen); |
| 1120 |
|
| 1121 |
$newValue = openssl_decrypt($raw_value, $method, $key, 0, $iv); |
| 1122 |
if (!$newValue || substr($newValue, -strlen($salt)) !== $salt) { |
| 1123 |
return false; |
| 1124 |
} |
| 1125 |
|
| 1126 |
return substr($newValue, 0, -strlen($salt)); |
| 1127 |
} |
| 1128 |
|
| 1129 |
|
| 1130 |
/** |
| 1131 |
* Get the welcome banner configuration. |
| 1132 |
* |
| 1133 |
* @return array The welcome banner configuration. |
| 1134 |
*/ |
| 1135 |
public static function getWelcomeBannerSettings() |
| 1136 |
{ |
| 1137 |
return Utility::getFromCache('welcome_banner_settings', function () { |
| 1138 |
$settings = Utility::getOption('welcome_banner_settings', []); |
| 1139 |
$defaults = [ |
| 1140 |
'login' => [ |
| 1141 |
'enabled' => 'no', |
| 1142 |
'description' => '', |
| 1143 |
'mediaType' => 'image', |
| 1144 |
'allowClose' => 'no', |
| 1145 |
'bannerImage' => '', |
| 1146 |
'bannerVideo' => [ |
| 1147 |
'type' => '', |
| 1148 |
'url' => '', |
| 1149 |
'content_type' => '', |
| 1150 |
'provider' => '', |
| 1151 |
'title' => '', |
| 1152 |
'author_name' => '', |
| 1153 |
'html' => '' |
| 1154 |
], |
| 1155 |
'ctaButtons' => [] |
| 1156 |
], |
| 1157 |
'logout' => [ |
| 1158 |
'enabled' => 'no', |
| 1159 |
'description' => '', |
| 1160 |
'mediaType' => 'image', |
| 1161 |
'useCustomUrl' => 'no', |
| 1162 |
'bannerImage' => '', |
| 1163 |
'bannerVideo' => [ |
| 1164 |
'type' => '', |
| 1165 |
'url' => '', |
| 1166 |
'content_type' => '', |
| 1167 |
'provider' => '', |
| 1168 |
'title' => '', |
| 1169 |
'author_name' => '', |
| 1170 |
'html' => '' |
| 1171 |
], |
| 1172 |
'ctaButtons' => [] |
| 1173 |
] |
| 1174 |
]; |
| 1175 |
$settings = wp_parse_args($settings, $defaults); |
| 1176 |
return $settings; |
| 1177 |
}, WEEK_IN_SECONDS); |
| 1178 |
} |
| 1179 |
|
| 1180 |
public static function getWelcomeBanner($view = 'login') |
| 1181 |
{ |
| 1182 |
$settings = self::getWelcomeBannerSettings(); |
| 1183 |
$welcomeBanner = Arr::get($settings, $view, []); |
| 1184 |
if (Arr::get($welcomeBanner, 'enabled') != 'yes') { |
| 1185 |
return null; |
| 1186 |
} |
| 1187 |
|
| 1188 |
unset($welcomeBanner['description']); |
| 1189 |
|
| 1190 |
return $welcomeBanner; |
| 1191 |
} |
| 1192 |
|
| 1193 |
public static function getEnabledFeedLinks() |
| 1194 |
{ |
| 1195 |
$links = array_filter(self::getFeedLinks(), function ($item) { |
| 1196 |
return Arr::get($item, 'enabled') == 'yes' && Arr::get($item, 'is_unavailable') != 'yes'; |
| 1197 |
}); |
| 1198 |
|
| 1199 |
return array_values($links); |
| 1200 |
} |
| 1201 |
|
| 1202 |
public static function getFeedLinks() |
| 1203 |
{ |
| 1204 |
return Utility::getFromCache('feed_links', function () { |
| 1205 |
return Utility::getOption('feed_links', []); |
| 1206 |
}, WEEK_IN_SECONDS); |
| 1207 |
} |
| 1208 |
|
| 1209 |
public static function updateFeedLinks($links) |
| 1210 |
{ |
| 1211 |
Utility::updateOption('feed_links', $links); |
| 1212 |
Utility::setCache('feed_links', $links, WEEK_IN_SECONDS); |
| 1213 |
} |
| 1214 |
|
| 1215 |
/** |
| 1216 |
* Get the full name of a WordPress user. |
| 1217 |
* |
| 1218 |
* @param int|null $id The ID of the user. |
| 1219 |
* @return string The full name of the user. |
| 1220 |
*/ |
| 1221 |
public static function getWpUserFullName($id = null) |
| 1222 |
{ |
| 1223 |
$id = $id ?: get_current_user_id(); |
| 1224 |
$user = get_user_by('ID', $id); |
| 1225 |
|
| 1226 |
$fullName = $user->display_name; |
| 1227 |
if ($user->first_name && $user->last_name) { |
| 1228 |
$fullName = $user->first_name . ' ' . $user->last_name; |
| 1229 |
} |
| 1230 |
|
| 1231 |
return $fullName; |
| 1232 |
} |
| 1233 |
|
| 1234 |
/** |
| 1235 |
* Get the onboarding settings. |
| 1236 |
* |
| 1237 |
* @return array The onboarding settings. |
| 1238 |
*/ |
| 1239 |
public static function getOnboardingSettings() |
| 1240 |
{ |
| 1241 |
$default = [ |
| 1242 |
'is_onboarding_enabled' => 'no', |
| 1243 |
'registration_page_url' => '', |
| 1244 |
]; |
| 1245 |
|
| 1246 |
$settings = Utility::getOption('onboarding_settings', $default); |
| 1247 |
|
| 1248 |
return wp_parse_args($settings, $default); |
| 1249 |
} |
| 1250 |
|
| 1251 |
/** |
| 1252 |
* Get all WordPress published pages. |
| 1253 |
* |
| 1254 |
* @return array An array of page data. |
| 1255 |
*/ |
| 1256 |
public static function getAllWpPublishedPage() |
| 1257 |
{ |
| 1258 |
$posts = get_posts(array( |
| 1259 |
'post_status' => 'publish', |
| 1260 |
'numberposts' => -1, |
| 1261 |
'post_type' => 'any', |
| 1262 |
)); |
| 1263 |
|
| 1264 |
return array_map(function ($post) { |
| 1265 |
return array( |
| 1266 |
'id' => $post->ID, |
| 1267 |
'permalink' => get_permalink($post), |
| 1268 |
'title' => get_the_title($post), |
| 1269 |
); |
| 1270 |
}, $posts); |
| 1271 |
} |
| 1272 |
|
| 1273 |
/** |
| 1274 |
* Add a user to a space. |
| 1275 |
* |
| 1276 |
* @param Space | int $space space to add the user to. |
| 1277 |
* @param int $userId The ID of the user to add. |
| 1278 |
* @param string $role The role of the user in the space. |
| 1279 |
* @param string $by The source of the action. |
| 1280 |
* @return bool True if the user was added, false otherwise. |
| 1281 |
*/ |
| 1282 |
public static function addToSpace($space, $userId, $role = 'member', $by = 'self') |
| 1283 |
{ |
| 1284 |
if (is_numeric($space)) { |
| 1285 |
$space = Space::find($space); |
| 1286 |
} |
| 1287 |
|
| 1288 |
if (!$space) { |
| 1289 |
return false; |
| 1290 |
} |
| 1291 |
|
| 1292 |
$exist = SpaceUserPivot::where('user_id', $userId) |
| 1293 |
->where('space_id', $space->id) |
| 1294 |
->first(); |
| 1295 |
|
| 1296 |
if ($exist) { |
| 1297 |
if ($exist->status != 'active') { |
| 1298 |
$exist->status = 'active'; |
| 1299 |
$exist->role = $role; |
| 1300 |
$exist->save(); |
| 1301 |
return true; |
| 1302 |
} |
| 1303 |
|
| 1304 |
return false; |
| 1305 |
} |
| 1306 |
|
| 1307 |
SpaceUserPivot::create([ |
| 1308 |
'space_id' => $space->id, |
| 1309 |
'role' => $role, |
| 1310 |
'user_id' => $userId |
| 1311 |
]); |
| 1312 |
|
| 1313 |
if ($space->type == 'course') { |
| 1314 |
do_action('fluent_community/course/enrolled', $space, $userId, $by); |
| 1315 |
} else { |
| 1316 |
do_action('fluent_community/space/joined', $space, $userId, $by); |
| 1317 |
} |
| 1318 |
|
| 1319 |
return true; |
| 1320 |
} |
| 1321 |
|
| 1322 |
/** |
| 1323 |
* Remove a user from a space if exist. |
| 1324 |
* |
| 1325 |
* @param int $userId The ID of the user. |
| 1326 |
* @param int $spaceId The ID of the space. |
| 1327 |
* @param string $by The source of the action. self | by_admin |
| 1328 |
* @return bool True if the user is in the space, false otherwise. |
| 1329 |
*/ |
| 1330 |
public static function removeFromSpace($space, $userId, $by = 'self') |
| 1331 |
{ |
| 1332 |
$user = User::find($userId); |
| 1333 |
if (!$user) { |
| 1334 |
return false; |
| 1335 |
} |
| 1336 |
|
| 1337 |
if (is_numeric($space)) { |
| 1338 |
$space = Space::find($space); |
| 1339 |
} |
| 1340 |
|
| 1341 |
if (!$space) { |
| 1342 |
return false; |
| 1343 |
} |
| 1344 |
|
| 1345 |
if (!self::isUserInSpace($userId, $space->id)) { |
| 1346 |
return false; |
| 1347 |
} |
| 1348 |
|
| 1349 |
SpaceUserPivot::bySpace($space->id) |
| 1350 |
->byUser(get_current_user_id()) |
| 1351 |
->delete(); |
| 1352 |
|
| 1353 |
$user->cacheAccessSpaces(); |
| 1354 |
do_action('fluent_community/space/user_left', $space, get_current_user_id(), $by); |
| 1355 |
|
| 1356 |
return true; |
| 1357 |
} |
| 1358 |
|
| 1359 |
/** |
| 1360 |
* Render a link with icon. |
| 1361 |
* |
| 1362 |
* @param array $link The link data. |
| 1363 |
* @param string $linkClass Additional classes for the link. |
| 1364 |
* @param string $fallback The fallback content if no icon is found. |
| 1365 |
* @param bool $renderIcon Whether to render the icon or not. |
| 1366 |
*/ |
| 1367 |
public static function renderLink($link, $linkClass = '', $fallback = '<span class="fcom_no_avatar"></span>', $renderIcon = true) |
| 1368 |
{ |
| 1369 |
$linkAtts = array_filter([ |
| 1370 |
'class' => trim($linkClass . ' ' . Arr::get($link, 'link_classes')) . ' fcom_compt_link', |
| 1371 |
'target' => Arr::get($link, 'new_tab') === 'yes' ? '_blank' : '', |
| 1372 |
'rel' => Arr::get($link, 'new_tab') === 'yes' ? 'noopener noreferrer' : '', |
| 1373 |
]); |
| 1374 |
?> |
| 1375 |
<a aria-label="Go to <?php echo esc_attr(Arr::get($link, 'title')); ?> page" |
| 1376 |
href="<?php echo esc_url($link['permalink']); ?>" <?php foreach ($linkAtts as $key => $value) { |
| 1377 |
echo esc_attr($key) . '="' . esc_attr($value) . '"'; |
| 1378 |
} ?>> |
| 1379 |
<?php $renderIcon && self::printLinkIcon($link, $fallback); ?> |
| 1380 |
<span class="community_name"><?php echo wp_kses_post(Arr::get($link, 'title')); ?></span> |
| 1381 |
<?php if (Arr::get($link, 'show_lock')): ?> |
| 1382 |
<span class="fcom_space_lock"> |
| 1383 |
<i class="el-icon"> |
| 1384 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"> |
| 1385 |
<path fill="currentColor" |
| 1386 |
d="M224 448a32 32 0 0 0-32 32v384a32 32 0 0 0 32 32h576a32 32 0 0 0 32-32V480a32 32 0 0 0-32-32zm0-64h576a96 96 0 0 1 96 96v384a96 96 0 0 1-96 96H224a96 96 0 0 1-96-96V480a96 96 0 0 1 96-96"></path> |
| 1387 |
<path fill="currentColor" |
| 1388 |
d="M512 544a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V576a32 32 0 0 1 32-32m192-160v-64a192 192 0 1 0-384 0v64zM512 64a256 256 0 0 1 256 256v128H256V320A256 256 0 0 1 512 64"></path> |
| 1389 |
</svg> |
| 1390 |
</i> |
| 1391 |
</span> |
| 1392 |
<?php endif; ?> |
| 1393 |
|
| 1394 |
</a> |
| 1395 |
<?php |
| 1396 |
} |
| 1397 |
|
| 1398 |
/** |
| 1399 |
* Print a link icon. |
| 1400 |
* |
| 1401 |
* @param array $link The link data. |
| 1402 |
* @param string $fallback The fallback content if no icon is found. |
| 1403 |
*/ |
| 1404 |
public static function printLinkIcon($link, $fallback = '<span class="fcom_no_avatar"></span>') |
| 1405 |
{ |
| 1406 |
?> |
| 1407 |
<?php if ($img = Arr::get($link, 'icon_image')): ?> |
| 1408 |
<div class="community_avatar"> |
| 1409 |
<img alt="" src="<?php echo esc_url($img); ?>"/> |
| 1410 |
</div> |
| 1411 |
<?php elseif ($emoji = Arr::get($link, 'emoji')): ?> |
| 1412 |
<div class="community_avatar"> |
| 1413 |
<span class="fcom_emoji"><?php echo esc_html($emoji); ?></span> |
| 1414 |
</div> |
| 1415 |
<?php elseif ($svg = Arr::get($link, 'shape_svg')): ?> |
| 1416 |
<div class="community_avatar"> |
| 1417 |
<span class="fcom_shape"><i |
| 1418 |
class="el-icon"><?php echo \FluentCommunity\App\Services\CustomSanitizer::sanitizeSvg($svg); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></i></span> |
| 1419 |
</div> |
| 1420 |
<?php else: |
| 1421 |
echo '<div class="community_avatar">' . wp_kses_post($fallback) . '</div>'; |
| 1422 |
endif; |
| 1423 |
} |
| 1424 |
|
| 1425 |
/** |
| 1426 |
* Get the IP address of the user. |
| 1427 |
* |
| 1428 |
* @param bool $anonymize Whether to anonymize the IP address. |
| 1429 |
* @return string The IP address. |
| 1430 |
*/ |
| 1431 |
public static function getIp($anonymize = false) |
| 1432 |
{ |
| 1433 |
static $ipAddress; |
| 1434 |
|
| 1435 |
if ($ipAddress) { |
| 1436 |
return $ipAddress; |
| 1437 |
} |
| 1438 |
|
| 1439 |
if (empty($_SERVER['REMOTE_ADDR'])) { |
| 1440 |
// It's a local cli request |
| 1441 |
return '127.0.0.1'; |
| 1442 |
} |
| 1443 |
|
| 1444 |
$ipAddress = ''; |
| 1445 |
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { |
| 1446 |
$ipAddress = sanitize_text_field(wp_unslash($_SERVER["REMOTE_ADDR"])); |
| 1447 |
//If it's a valid Cloudflare request |
| 1448 |
if (self::isCfIp($ipAddress)) { |
| 1449 |
//Use the CF-Connecting-IP header. |
| 1450 |
$ipAddress = sanitize_text_field(wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP'])); |
| 1451 |
} |
| 1452 |
} else if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') { |
| 1453 |
// most probably it's local reverse proxy |
| 1454 |
if (isset($_SERVER["HTTP_CLIENT_IP"])) { |
| 1455 |
$ipAddress = sanitize_text_field(wp_unslash($_SERVER["HTTP_CLIENT_IP"])); |
| 1456 |
} else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { |
| 1457 |
$ipAddress = (string)rest_is_ip_address(trim(current(preg_split('/,/', sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR'])))))); |
| 1458 |
} |
| 1459 |
} |
| 1460 |
|
| 1461 |
if (!$ipAddress) { |
| 1462 |
$ipAddress = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); |
| 1463 |
} |
| 1464 |
|
| 1465 |
$ipAddress = preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ipAddress); |
| 1466 |
|
| 1467 |
$ipAddress = apply_filters('fluent_auth/user_ip', $ipAddress); |
| 1468 |
|
| 1469 |
if ($anonymize) { |
| 1470 |
return wp_privacy_anonymize_ip($ipAddress); |
| 1471 |
} |
| 1472 |
|
| 1473 |
return $ipAddress; |
| 1474 |
} |
| 1475 |
|
| 1476 |
/** |
| 1477 |
* Check if the IP address is from Cloudflare. |
| 1478 |
* |
| 1479 |
* @param string $ip The IP address to check. |
| 1480 |
* @return bool True if the IP is from Cloudflare, false otherwise. |
| 1481 |
*/ |
| 1482 |
public static function isCfIp($ip = '') |
| 1483 |
{ |
| 1484 |
if (!$ip && isset($_SERVER["REMOTE_ADDR"])) { |
| 1485 |
$ip = sanitize_text_field(wp_unslash($_SERVER["REMOTE_ADDR"])); |
| 1486 |
} |
| 1487 |
|
| 1488 |
if (!$ip) { |
| 1489 |
return false; |
| 1490 |
} |
| 1491 |
|
| 1492 |
$cloudflareIPRanges = array( |
| 1493 |
'173.245.48.0/20', |
| 1494 |
'103.21.244.0/22', |
| 1495 |
'103.22.200.0/22', |
| 1496 |
'103.31.4.0/22', |
| 1497 |
'141.101.64.0/18', |
| 1498 |
'108.162.192.0/18', |
| 1499 |
'190.93.240.0/20', |
| 1500 |
'188.114.96.0/20', |
| 1501 |
'197.234.240.0/22', |
| 1502 |
'198.41.128.0/17', |
| 1503 |
'162.158.0.0/15', |
| 1504 |
'104.16.0.0/13', |
| 1505 |
'104.24.0.0/14', |
| 1506 |
'172.64.0.0/13', |
| 1507 |
'131.0.72.0/22', |
| 1508 |
); |
| 1509 |
|
| 1510 |
//Make sure that the request came via Cloudflare. |
| 1511 |
foreach ($cloudflareIPRanges as $range) { |
| 1512 |
//Use the ip_in_range function from Joomla. |
| 1513 |
if (self::ipInRange($ip, $range)) { |
| 1514 |
//IP is valid. Belongs to Cloudflare. |
| 1515 |
return true; |
| 1516 |
} |
| 1517 |
} |
| 1518 |
|
| 1519 |
return false; |
| 1520 |
} |
| 1521 |
|
| 1522 |
/** |
| 1523 |
* Check if the IP address is in the given range. |
| 1524 |
* |
| 1525 |
* @param string $ip The IP address to check. |
| 1526 |
* @param string $range The range to check against. |
| 1527 |
* @return bool True if the IP is in the range, false otherwise. |
| 1528 |
*/ |
| 1529 |
private static function ipInRange($ip, $range) |
| 1530 |
{ |
| 1531 |
if (strpos($range, '/') !== false) { |
| 1532 |
// $range is in IP/NETMASK format |
| 1533 |
list($range, $netmask) = explode('/', $range, 2); |
| 1534 |
if (strpos($netmask, '.') !== false) { |
| 1535 |
// $netmask is a 255.255.0.0 format |
| 1536 |
$netmask = str_replace('*', '0', $netmask); |
| 1537 |
$netmask_dec = ip2long($netmask); |
| 1538 |
return ((ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec)); |
| 1539 |
} else { |
| 1540 |
// $netmask is a CIDR size block |
| 1541 |
// fix the range argument |
| 1542 |
$x = explode('.', $range); |
| 1543 |
while (count($x) < 4) $x[] = '0'; |
| 1544 |
list($a, $b, $c, $d) = $x; |
| 1545 |
$range = sprintf("%u.%u.%u.%u", empty($a) ? '0' : $a, empty($b) ? '0' : $b, empty($c) ? '0' : $c, empty($d) ? '0' : $d); |
| 1546 |
$range_dec = ip2long($range); |
| 1547 |
$ip_dec = ip2long($ip); |
| 1548 |
|
| 1549 |
# Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s |
| 1550 |
#$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0')); |
| 1551 |
|
| 1552 |
# Strategy 2 - Use math to create it |
| 1553 |
$wildcard_dec = pow(2, (32 - $netmask)) - 1; |
| 1554 |
$netmask_dec = ~$wildcard_dec; |
| 1555 |
|
| 1556 |
return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec)); |
| 1557 |
} |
| 1558 |
} else { |
| 1559 |
// range might be 255.255.*.* or 1.2.3.0-1.2.3.255 |
| 1560 |
if (strpos($range, '*') !== false) { // a.b.*.* format |
| 1561 |
// Just convert to A-B format by setting * to 0 for A and 255 for B |
| 1562 |
$lower = str_replace('*', '0', $range); |
| 1563 |
$upper = str_replace('*', '255', $range); |
| 1564 |
$range = "$lower-$upper"; |
| 1565 |
} |
| 1566 |
|
| 1567 |
if (strpos($range, '-') !== false) { // A-B format |
| 1568 |
list($lower, $upper) = explode('-', $range, 2); |
| 1569 |
$lower_dec = (float)sprintf("%u", ip2long($lower)); |
| 1570 |
$upper_dec = (float)sprintf("%u", ip2long($upper)); |
| 1571 |
$ip_dec = (float)sprintf("%u", ip2long($ip)); |
| 1572 |
return (($ip_dec >= $lower_dec) && ($ip_dec <= $upper_dec)); |
| 1573 |
} |
| 1574 |
return false; |
| 1575 |
} |
| 1576 |
} |
| 1577 |
|
| 1578 |
public static function getPortalRequestPath($requestUri) |
| 1579 |
{ |
| 1580 |
$portalSlug = self::getPortalSlug(); |
| 1581 |
|
| 1582 |
if ($portalSlug == $requestUri) { |
| 1583 |
return 'portal_home'; |
| 1584 |
} |
| 1585 |
|
| 1586 |
if (!$requestUri) { |
| 1587 |
return false; |
| 1588 |
} |
| 1589 |
|
| 1590 |
if ($portalSlug) { |
| 1591 |
// remove the portal slug from the request uri. Don't use str_replace as it will replace all occurrences |
| 1592 |
$requestUri = substr($requestUri, strlen($portalSlug)); |
| 1593 |
} |
| 1594 |
|
| 1595 |
$parts = explode('/', $requestUri); |
| 1596 |
$start = $parts[0]; |
| 1597 |
|
| 1598 |
$routeStats = [ |
| 1599 |
'fcom_route', |
| 1600 |
'portal_home', |
| 1601 |
'admin', |
| 1602 |
'post', |
| 1603 |
'space', |
| 1604 |
'discover', |
| 1605 |
'members', |
| 1606 |
'u', |
| 1607 |
'notifications', |
| 1608 |
'bookmarks', |
| 1609 |
'courses', |
| 1610 |
'course', |
| 1611 |
'leaderboards' |
| 1612 |
]; |
| 1613 |
|
| 1614 |
if (in_array($start, $routeStats)) { |
| 1615 |
return $requestUri; |
| 1616 |
} |
| 1617 |
return false; |
| 1618 |
} |
| 1619 |
} |
| 1620 |
|