isCommunityModerator(); } /** * Get the URL for an asset file. * * @param string $file The file name. * @return string The full URL to the asset. */ public static function assetUrl($file = '') { return FLUENT_COMMUNITY_PLUGIN_URL . 'assets/' . $file; } /** * Get the base URL for the portal. * * @param string $path The path to append to the base URL. * @return string The full base URL. */ public static function baseUrl($path = '') { $baseUrl = apply_filters('fluent_community/base_url', home_url(self::getPortalSlug())); $baseUrl = rtrim($baseUrl, '/'); if (self::getPortalRouteType() != 'hash') { return $baseUrl . '/' . ltrim($path, '/'); } if (!$path) { return $baseUrl . '/'; } return $baseUrl . '/#/' . ltrim($path, '/'); } public static function getAuthUrl() { $settings = self::generalSettings(); return Arr::get($settings, 'cutsom_auth_url', ''); } /** * Get the space IDs for a user. * * @param int|null $userId The user ID. If null, uses the current user. * @return array An array of space IDs. */ public static function getUserSpaceIds($userId = null) { if (!$userId) { $userId = get_current_user_id(); } return SpaceUserPivot::where('user_id', $userId) ->where('status', 'active') ->pluck('space_id') ->toArray(); } public static function getUserSpaces($userId = null) { if (!$userId) { $userId = get_current_user_id(); } return Space::whereHas('members', function ($query) use ($userId) { $query->where('user_id', $userId); })->get(); } /** * Check if a user is in a specific space. * * @param int $userId The user ID. * @param int $spaceId The space ID. * @return bool True if the user is in the space, false otherwise. */ public static function isUserInSpace($userId, $spaceId) { return SpaceUserPivot::where('user_id', $userId) ->where('space_id', $spaceId) ->where('status', 'active') ->exists(); } /** * Generate HTML attributes from an array. * * @param array $atts An array of attribute key-value pairs. * @return string The generated HTML attributes string. */ public static function attrs($atts = []) { $text = ''; foreach ($atts as $key => $value) { $text .= "$key=\"$value\" "; } return $text; } /** * Get media from a URL. * * @param string|array $url The URL or an array containing URL information. * @return Media|null The Media object if found, null otherwise. */ public static function getMediaFromUrl($url) { if (is_array($url) && isset($url['provider'])) { $provider = Arr::get($url, 'provider'); if ($provider == 'giphy') { return null; } $url = Arr::get($url, 'url'); } if (!$url) { return null; } $parsedUrl = wp_parse_url($url, PHP_URL_QUERY); if (!$parsedUrl) { return null; } // Parse the query string to get the media_key value parse_str($parsedUrl, $queryParams); $key = Arr::get($queryParams, 'media_key'); if (!$key) { return null; } return Media::where('media_key', $key)->first(); } /** * Get media items from multiple URLs. * * @param array $urls An array of URLs. * @return array An array of Media objects. */ public static function getMediaItemsFromUrl($urls) { $mediaItems = []; foreach ($urls as $url) { $media = self::getMediaFromUrl($url); if ($media) { $mediaItems[] = $media; } } return $mediaItems; } /** * Get general settings for the community. * * @param bool $cached Whether to use cached settings. * @return array The general settings. */ public static function generalSettings($cached = true) { static $settings = null; if ($cached && $settings) { return $settings; } $settings = get_option('fluent_community_settings', []); $defaults = [ 'site_title' => get_bloginfo('name'), 'slug' => 'portal', 'logo' => '', 'white_logo' => '', 'featured_image' => '', 'access' => [ 'acess_level' => 'public', // logged_in, public, role_based 'access_roles' => [] ], 'auth_form_type' => 'default', 'disable_global_posts' => 'yes', 'auth_content' => 'Please login first to access this page', 'auth_redirect' => '', 'restricted_role_content' => 'Sorry, you can not access to this page. Only authorized users can access this page.', 'auth_url' => '', 'cutsom_auth_url' => self::baseUrl('?fcom_action=auth'), ]; $settings = wp_parse_args($settings, $defaults); if ($settings['auth_form_type'] != 'custom' || empty($settings['auth_form_type'])) { $settings['cutsom_auth_url'] = self::baseUrl('?fcom_action=auth'); } if (defined('FLUENT_COMMUNITY_PORTAL_SLUG')) { $settings['slug'] = \FLUENT_COMMUNITY_PORTAL_SLUG; $settings['is_slug_defined'] = true; } else { unset($settings['is_slug_defined']); } return $settings; } public static function hasGlobalPost() { $settings = self::generalSettings(); $status = Arr::get($settings, 'disable_global_posts', '') != 'yes'; return apply_filters('fluent_community/has_global_post', $status); } /** * Check if a user can access the portal. * * @param int|null $userId The user ID. If null, uses the current user. * @return bool True if the user can access the portal, false otherwise. */ public static function canAccessPortal($userId = null) { $settings = self::generalSettings(); $accessLevel = Arr::get($settings, 'access.acess_level'); if ($accessLevel == 'public') { return apply_filters('fluent_community/can_access_portal', true); } if (!$userId) { $userId = get_current_user_id(); } if (!$userId) { return apply_filters('fluent_community/can_access_portal', false); } if ($accessLevel == 'logged_in') { return apply_filters('fluent_community/can_access_portal', true); } if (user_can($userId, 'edit_pages')) { return apply_filters('fluent_community/can_access_portal', true); } $roles = Arr::get($settings, 'access.access_roles', []); $user = get_user_by('ID', $userId); if (!$user) { return apply_filters('fluent_community/can_access_portal', false); } $result = !!array_intersect(array_values($user->roles), $roles); if (!$result) { return apply_filters('fluent_community/can_access_portal', false); } $xProfile = Helper::getCurrentProfile(); $result = $xProfile && $xProfile->status == 'active'; return apply_filters('fluent_community/can_access_portal', $result); } /** * Get the portal route paths. * * @return array An array of portal route paths. */ public static function portalRoutePaths() { return apply_filters('fluent_community/app_route_paths', [ 'portal_home', 'space', 'discover', 'members', 'courses', 'u', 'leaderboards', 'chat', 'notifications', 'bookmarks', 'post', 'admin' ]); } /** * Get the current user's profile. * * @param bool $cached Whether to use cached profile. * @return XProfile|null The user's profile or null if not found. */ public static function getCurrentProfile($cached = true) { $userId = get_current_user_id(); if (!$userId) { return null; } static $profile; if ($profile && $cached) { return $profile; } if (!$userId) { return null; } $profile = XProfile::where('user_id', $userId)->first(); return $profile; } /** * Get the current user Model. * * @param bool $cached Whether to use cached user. * @return User|false The User model or false if not found. */ public static function getCurrentUser($cached = true) { $userId = get_current_user_id(); if (!$userId) { return false; } static $user; if ($user && $cached) { return $user; } $user = User::find($userId); return $user; } /** * Get the route paths for the community. * * @return array An array of route paths. */ private static function getRoutePaths() { return [ 'dashboard' => '/dashboard', 'all_feeds' => '/', 'single_feed' => '/post/:feed_slug', 'space_feeds' => '/space/:space/home', 'space_feed' => '/space/:space/post/:feed_slug', 'space_members' => '/space/:space/members', 'spaces' => '/discover/spaces', 'settings' => '/admin/settings', 'admin_moderators' => '/admin/settings/moderators', 'all_members' => '/members', 'user_profile' => '/u/:username/', 'user_communities' => '/u/:username/spaces', 'update_profile' => '/u/:username/update', 'discussions' => '/discussions', 'create_topic' => '/discussions/create-topic', 'topic' => '/discussions/topic/:slug', 'notifications' => '/notifications', 'bookmarks' => '/bookmarks', 'courses' => '/courses', 'view_course' => '/courses/view/:course_id/lessons', 'view_lesson' => '/courses/view/:course_id/lessons/:lesson_slug/view', 'manage_courses' => '/admin/manage-courses', 'edit_lessons' => '/admin/manage-courses/edit/:course_id/lessons', 'course_students' => '/admin/manage-courses/edit/:course_id/students', 'course_overview' => '/admin/manage-courses/edit/:course_id/overview', 'manage_leaderboard' => '/admin/manage-leaderboard', ]; } /** * Get the URL for a JavaScript route. * * @param array $route The route information. * @return string The URL for the route. */ public static function getUrlByJsRoute($route = []) { $routePaths = self::getRoutePaths(); $routeName = Arr::get($route, 'name', ''); if (!$routeName || !isset($routePaths[$routeName])) { return self::baseUrl(); } $path = $routePaths[$routeName]; $params = (array)Arr::get($route, 'params', []); if (!$params) { return self::baseUrl($path); } $replaces = []; foreach ($params as $paramKey => $paramValue) { $replaces[':' . $paramKey] = $paramValue; } $path = str_replace(array_keys($replaces), array_values($replaces), $path); return self::baseUrl($path); } /** * Get the route name from a request path. * * @param string $path The request path. * @return string|false The route name or false if not found. */ public static function getRouteNameByRequestPath($path) { $path = '//' . $path; if (strpos($path, '/u/')) { return 'user_profile'; } if (strpos($path, '/post/')) { return 'feed_view'; } if (strpos($path, '/lessons/')) { return 'lesson_view'; } if (strpos($path, '/course/')) { return 'course_view'; } if (strpos($path, '/space/') && !strpos($path, '/discover/spaces')) { return 'community_view'; } if (strpos($path, '/admin')) { return 'admin'; } return false; } /** * Get a human-readable excerpt from content. * * @param string $content The content to extract from. * @param int $length The maximum length of the excerpt. * @return string The human-readable excerpt. */ public static function getHumanExcerpt($content, $length = 100) { if ($content) { $patterns = [ '/^#{1,6}\s+/m' => '', // Bold and Italic: remove '*' and '_' symbols '/(\*\*|__)(.*?)\1/' => '$2', '/(\*|_)(.*?)\1/' => '$2', // Code blocks: remove triple backticks '/^```\s*\w*\s*\n([\s\S]*?)\n```\s*$/m' => '$1', // Inline code: remove single backticks '/`([^`]+)`/' => '$1', // Blockquotes: remove '>' symbol '/^\s*>\s?/m' => '', // Horizontal rules: replace with empty line '/^\s*([-*_])\1{2,}\s*$/m' => "\n", // Links: keep only the link text '/\[([^\]]+)\]\([^\)]+\)/' => '$1', // Images: keep only the alt text '/!\[([^\]]+)\]\([^\)]+\)/' => '$1', // Strikethrough: remove '~~' symbols '/~~(.*?)~~/' => '$1', // Task lists: remove checkbox syntax '/^\s*[-*+]\s+\[[ xX]\]\s+/m' => '', ]; $content = preg_replace(array_keys($patterns), array_values($patterns), $content); // remove all tags $content = wp_strip_all_tags($content); // remove new lines and tabs $content = str_replace(["\r", "\n", "\t"], ' ', $content); // remove multiple spaces $content = preg_replace('/\s+/', ' ', $content); // trim $content = trim($content); } if (!$content) { return ''; } if (mb_strlen($content) <= $length) { return $content; } // return the first $length chars of the content with ... at the end return mb_substr($content, 0, $length) . '...'; } /** * Check if the portal is publicly accessible. * * @return bool True if publicly accessible, false otherwise. */ public static function isPublicAccessible() { $settings = self::generalSettings(); return Arr::get($settings, 'access.acess_level') == 'public'; } /** * Get media by provider. * * @param array $images The array of images. * @param string $provider The provider to filter by. * @return array The filtered array of images. */ public static function getMediaByProvider($images, $provider = 'uploader') { if (is_array($images)) { return array_filter($images, function ($image) use ($provider) { if (is_array($image)) { if (isset($image['provider'])) { return Arr::get($image, 'provider') == $provider; } return $provider === 'uploader'; // for existing images when no provider was set. } }); } return []; } /** * Get the community menu groups. * * @param User|null $user The user to get menu groups for. * @return array The community menu groups. */ public static function getCommunityMenuGroups($user = null) { if (!$user) { $user = self::getCurrentUser(); } $communityGroups = self::getAllCommunityGroups($user); if ($communityGroups->isEmpty()) { return []; } $isComModerator = $user && $user->hasCommunityModeratorAccess(); $isCourseCreator = $user && $user->hasCourseCreatorAccess(); $formattedGroups = []; foreach ($communityGroups as $communityGroup) { $spaces = $communityGroup->spaces; $validSpaces = []; $isShowAll = Arr::get($communityGroup->settings, 'always_show_spaces') === 'yes'; foreach ($spaces as $space) { if ($isComModerator && $space->type != 'course') { $validSpaces[] = self::transformSpaceToLink($space); continue; } if ($isCourseCreator && $space->type == 'course') { $validSpaces[] = self::transformSpaceToLink($space); continue; } if ($space->privacy === 'secret') { if (!$user || !$space->getMembership($user->ID)) { continue; } $validSpaces[] = self::transformSpaceToLink($space); continue; } if ($isShowAll || $space->privacy = 'public') { $validSpace = self::transformSpaceToLink($space); if ($space->privacy == 'private') { if (!$user || !$space->getMembership($user->ID)) { $validSpace['show_lock'] = true; } } $validSpaces[] = $validSpace; continue; } if (!$user || $space->getMembership($user->ID)) { continue; } $validSpaces[] = self::transformSpaceToLink($space); } if (!$validSpaces && !$isComModerator && !$isCourseCreator) { continue; } $formattedGroups[] = [ 'id' => $communityGroup->id, 'title' => $communityGroup->title, 'slug' => $communityGroup->slug, 'logo' => $communityGroup->logo, 'children' => $validSpaces ]; } return apply_filters('fluent_community/menu_groups_for_user', $formattedGroups, $user); } /** * Transform a space to a link array. * * @param Space $space The space to transform. * @return array The transformed space link array. */ private static function transformSpaceToLink($space) { $logo = $space->logo; $title = $space->title; if ($space->status == 'draft') { $title = $title . ' ' . __('(Draft)', 'fluent-community'); } return [ 'title' => $title, 'icon_image' => $logo, 'shape_svg' => !$logo ? Arr::get($space->settings, 'shape_svg', '') : '', 'emoji' => !$logo ? Arr::get($space->settings, 'emoji', '') : '', 'permalink' => $space->getPermalink(), 'link_classes' => 'space_menu_item route_url fcom_space_id_' . $space->id . ' fcom_space_' . $space->slug ]; } public static function isAlreadyOnboarded() { $communitySettings = get_option('fluent_community_settings', []); return !empty($communitySettings); } /** * Get all community groups. * * @param User $user The user to get groups for. * @param bool $willCreate Whether to create a default group if none exist. * @return \FluentCommunity\Framework\Support\Collection Collection of Groups */ public static function getAllCommunityGroups($user, $willCreate = true) { $isModerator = $user && $user->isCommunityModerator(); $communityGroups = SpaceGroup::query()->orderBy('serial', 'ASC') ->with([ 'spaces' => function ($query) use ($isModerator) { if ($isModerator) { $query->orderBy('serial', 'ASC'); } else { $query->where('status', 'published') ->orderBy('serial', 'ASC'); } } ]) ->get(); if ($communityGroups->isEmpty() && $willCreate) { $createdSpace = (new ActivationHandler(App::make()))->maybeCreateDefaultSpaceGroup(); if ($createdSpace) { Space::where('type', 'community')->update([ 'parent_id' => $createdSpace->id ]); } return self::getAllCommunityGroups($user, false); } return $communityGroups; } /** * Check if a feature is enabled. * * @param string $feature The feature to check. * @return bool True if the feature is enabled, false otherwise. */ public static function isFeatureEnabled($feature) { $features = Utility::getFeaturesConfig(); return isset($features[$feature]) && $features[$feature] === 'yes'; } /** * Get the menu items group. * * @param string $context The context for getting menu items. * @return array The menu items group. */ public static function getMenuItemsGroup($context = 'view') { static $menuGroups; if ($menuGroups && $context === 'view') { return $menuGroups; } $menuGroups = Utility::getOption('fluent_community_menu_groups', []); $membersPageStatus = Utility::canViewMembersPage() ? 'yes' : 'no'; $defaultMainMenuItems = [ 'all_feeds' => [ 'slug' => 'all_feeds', 'title' => __('Feed', 'fluent-community'), 'is_system' => 'yes', 'is_locked' => 'yes', 'enabled' => 'yes', 'permalink' => self::baseUrl('/'), 'link_classes' => 'fcom_dashboard route_url', 'shape_svg' => '' ], 'spaces' => [ 'slug' => 'spaces', 'title' => __('Spaces', 'fluent-community'), 'is_system' => 'yes', 'is_locked' => 'yes', 'enabled' => 'yes', 'permalink' => self::baseUrl('discover/spaces'), 'link_classes' => 'fcom_spaces route_url', 'shape_svg' => '' ], 'all_courses' => [ 'slug' => 'all_courses', 'title' => 'Courses', 'link_classes' => 'fcom_courses route_url', 'is_system' => 'yes', 'is_locked' => 'yes', 'enabled' => 'yes', 'is_unavailable' => self::isFeatureEnabled('course_module') ? 'no' : 'yes', 'permalink' => self::baseUrl('courses'), 'shape_svg' => '' ], 'all_members' => [ 'slug' => 'all_members', 'title' => __('Members', 'fluent-community'), 'is_system' => 'yes', 'is_locked' => 'yes', 'is_unavailable' => $membersPageStatus == 'yes' ? 'no' : 'yes', 'enabled' => $membersPageStatus, 'permalink' => self::baseUrl('members'), 'link_classes' => 'fcom_all_members route_url', 'shape_svg' => '', ], 'leaderboard' => [ 'slug' => 'leaderboard', 'is_system' => 'yes', 'is_locked' => 'yes', 'enabled' => 'yes', 'is_unavailable' => self::isFeatureEnabled('leader_board_module') ? 'no' : 'yes', 'title' => __('Leaderboard', 'fluent-community'), 'link_classes' => 'fcom_leaderboards route_url', 'permalink' => self::baseUrl('leaderboards'), 'shape_svg' => '' ] ]; $mainItems = Arr::get($menuGroups, 'mainMenuItems', []); if ($mainItems && is_array($mainItems)) { if (isset($mainItems['all_communities'])) { $mainItems['spaces'] = $defaultMainMenuItems['spaces']; unset($mainItems['all_communities']); } foreach ($mainItems as $index => &$item) { if (empty($item['slug'])) { unset($mainItems[$index]); continue; } $defaultItem = Arr::get($defaultMainMenuItems, $item['slug'], []); if ($defaultItem) { $preservedKeys = ['is_system', 'is_locked', 'is_unavailable', 'slug', 'shape_svg']; foreach ($preservedKeys as $key) { if (isset($defaultItem[$key])) { $item[$key] = Arr::get($defaultItem, $key); } } if (Arr::get($defaultItem, 'is_system') === 'yes') { $item['permalink'] = $defaultItem['permalink']; $item['emoji'] = ''; $item['icon_image'] = ''; $item['link_classes'] = $defaultItem['link_classes']; } } } } else { $mainItems = $defaultMainMenuItems; } $defaultProfileDropDownItems = [ 'my_spaces' => [ 'slug' => 'my_spaces', 'title' => __('My Spaces', 'fluent-community'), 'is_system' => 'yes', 'is_locked' => 'yes', 'enabled' => 'yes', 'permalink' => '#{{user_url}}/spaces', 'shape_svg' => '' ], 'bookmarks' => [ 'slug' => 'bookmarks', 'title' => __('Bookmarks', 'fluent-community'), 'is_system' => 'yes', 'is_locked' => 'yes', 'enabled' => 'yes', 'permalink' => self::baseUrl('bookmarks'), 'shape_svg' => '' ], 'logout' => [ 'slug' => 'logout', 'title' => __('Logout', 'fluent-community'), 'is_system' => 'yes', 'is_locked' => 'yes', 'enabled' => 'yes', 'permalink' => '#{{logout_url}}', 'shape_svg' => '' ] ]; $profileDropDownItems = Arr::get($menuGroups, 'profileDropdownItems', []); if ($profileDropDownItems && is_array($profileDropDownItems)) { unset($profileDropDownItems['profile']); foreach ($profileDropDownItems as $index => &$item) { if (empty($item['slug'])) { unset($profileDropDownItems[$index]); continue; } $defaultItem = Arr::get($defaultProfileDropDownItems, $item['slug'], []); if ($defaultItem) { $preservedKeys = ['is_system', 'is_locked', 'is_unavailable', 'slug', 'svg_icon']; foreach ($preservedKeys as $key) { if (isset($defaultItem[$key])) { $item[$key] = Arr::get($defaultItem, $key); } } if (Arr::get($defaultItem, 'is_system') === 'yes') { $item['permalink'] = $defaultItem['permalink']; if (empty($item['shape_svg'])) { $item['shape_svg'] = $defaultItem['shape_svg']; } } } } } else { $profileDropDownItems = $defaultProfileDropDownItems; } $beforeCommunityMenuItems = Arr::get($menuGroups, 'beforeCommunityMenuItems', []); $afterCommunityMenuGroups = Arr::get($menuGroups, 'afterCommunityLinkGroups', []); if (!is_array($beforeCommunityMenuItems)) { $beforeCommunityMenuItems = []; } if (!is_array($afterCommunityMenuGroups)) { $afterCommunityMenuGroups = []; } if ($context == 'view') { $mainItems = array_filter($mainItems, function ($item) { return Arr::get($item, 'enabled') === 'yes' && Arr::get($item, 'is_unavailable') !== 'yes'; }); $profileDropDownItems = array_filter($profileDropDownItems, function ($item) { return Arr::get($item, 'enabled') === 'yes' && Arr::get($item, 'is_unavailable') !== 'yes'; }); $beforeCommunityMenuItems = array_filter($beforeCommunityMenuItems, function ($item) { return Arr::get($item, 'enabled') === 'yes' && Arr::get($item, 'is_unavailable') !== 'yes'; }); $validGroups = []; foreach ($afterCommunityMenuGroups as $group) { if (empty($group['items']) || !is_array($group['items'])) { continue; } $group['items'] = array_filter($group['items'], function ($item) { return Arr::get($item, 'enabled') === 'yes' && Arr::get($item, 'is_unavailable') !== 'yes'; }); if ($group['items']) { $validGroups[] = $group; } } $afterCommunityMenuGroups = $validGroups; } $menuGroups['mainMenuItems'] = $mainItems; $menuGroups['profileDropdownItems'] = $profileDropDownItems; $menuGroups['beforeCommunityMenuItems'] = $beforeCommunityMenuItems; $menuGroups['afterCommunityLinkGroups'] = $afterCommunityMenuGroups; if ($context == 'view') { $menuGroups = apply_filters('fluent_community/menu_groups', $menuGroups); } return $menuGroups; } /** * Get the meta data for a space. * * @param int $spaceId The ID of the space. * @param string $key The meta key. * @param mixed $default The default value if the meta key is not found. * @return mixed The meta value or the default value if not found. */ public static function getSpaceMeta($spaceId, $key, $default = null) { $meta = Meta::where('object_type', 'space') ->where('meta_key', $key) ->where('object_id', $spaceId) ->first(); if (!$meta) { return $default; } return $meta->value; } /** * Update the meta data for a space. * * @param int $spaceId The ID of the space. * @param string $key The meta key. * @param mixed $value The meta value. * @return Meta The updated meta object. */ public static function updateSpaceMeta($spaceId, $key, $value) { $meta = Meta::where('object_type', 'space') ->where('meta_key', $key) ->where('object_id', $spaceId) ->first(); if ($meta) { $meta->value = $value; $meta->save(); } else { $meta = Meta::create([ 'object_type' => 'space', 'object_id' => $spaceId, 'meta_key' => $key, 'value' => $value ]); } return $meta; } /** * Encrypt or decrypt a value. * * @param string $value The value to encrypt or decrypt. * @param string $type The type of operation ('e' for encrypt, 'd' for decrypt). * @return string|false The encrypted or decrypted value or false if an error occurs. */ public static function encryptDecrypt($value, $type = 'e') { if (!$value) { return $value; } if (!extension_loaded('openssl')) { return $value; } if (defined('FLUENT_COM_ENCRYPT_SALT')) { $salt = FLUENT_COM_ENCRYPT_SALT; } else { $salt = (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) ? LOGGED_IN_SALT : 'this-is-a-fallback-salt-but-not-secure'; } if (defined('FLUENT_COM__ENCRYPT_KEY')) { $key = FLUENT_COM__ENCRYPT_KEY; } else { $key = (defined('LOGGED_IN_KEY') && '' !== LOGGED_IN_KEY) ? LOGGED_IN_KEY : 'this-is-a-fallback-key-but-not-secure'; } if ($type == 'e') { $method = 'aes-256-ctr'; $ivlen = openssl_cipher_iv_length($method); $iv = openssl_random_pseudo_bytes($ivlen); $raw_value = openssl_encrypt($value . $salt, $method, $key, 0, $iv); if (!$raw_value) { return false; } return base64_encode($iv . $raw_value); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } $raw_value = base64_decode($value, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $method = 'aes-256-ctr'; $ivlen = openssl_cipher_iv_length($method); $iv = substr($raw_value, 0, $ivlen); $raw_value = substr($raw_value, $ivlen); $newValue = openssl_decrypt($raw_value, $method, $key, 0, $iv); if (!$newValue || substr($newValue, -strlen($salt)) !== $salt) { return false; } return substr($newValue, 0, -strlen($salt)); } /** * Get the welcome banner configuration. * * @return array The welcome banner configuration. */ public static function getWelcomeBannerSettings() { return Utility::getFromCache('welcome_banner_settings', function () { $defaults = [ 'login' => [ 'enabled' => 'no', 'description' => '', 'mediaType' => 'image', 'allowClose' => 'no', 'bannerImage' => '', 'bannerVideo' => [ 'type' => 'oembed', 'url' => '', 'content_type' => '', 'provider' => '', 'title' => '', 'author_name' => '', 'html' => '' ], 'ctaButtons' => [] ], 'logout' => [ 'enabled' => 'no', 'description' => '', 'mediaType' => 'image', 'useCustomUrl' => 'no', 'bannerImage' => '', 'bannerVideo' => [ 'type' => 'oembed', 'url' => '', 'content_type' => '', 'provider' => '', 'title' => '', 'author_name' => '', 'html' => '' ], 'ctaButtons' => [] ] ]; $settings = Utility::getOption('welcome_banner_settings', []); $settings = wp_parse_args($settings, $defaults); if (empty(Arr::get($settings, 'login.bannerVideo'))) { $settings['login']['bannerVideo'] = $defaults['login']['bannerVideo']; } if (empty(Arr::get($settings, 'logout.bannerVideo'))) { $settings['logout']['bannerVideo'] = $defaults['logout']['bannerVideo']; } return $settings; }, WEEK_IN_SECONDS); } public static function getWelcomeBanner($view = 'login') { $settings = self::getWelcomeBannerSettings(); $welcomeBanner = Arr::get($settings, $view, []); if (Arr::get($welcomeBanner, 'enabled') != 'yes') { return null; } unset($welcomeBanner['description']); if ($view == 'login') { return apply_filters('fluent_community/welcome_banner_for_logged_in', $welcomeBanner); } return apply_filters('fluent_community/welcome_banner_for_guests', $welcomeBanner); } public static function getEnabledFeedLinks() { $links = array_filter(self::getFeedLinks(), function ($item) { return Arr::get($item, 'enabled') == 'yes' && Arr::get($item, 'is_unavailable') != 'yes'; }); return array_values($links); } public static function getFeedLinks() { return Utility::getFromCache('feed_links', function () { return Utility::getOption('feed_links', []); }, WEEK_IN_SECONDS); } public static function updateFeedLinks($links) { Utility::updateOption('feed_links', $links); Utility::setCache('feed_links', $links, WEEK_IN_SECONDS); } /** * Get the full name of a WordPress user. * * @param int|null $id The ID of the user. * @return string The full name of the user. */ public static function getWpUserFullName($id = null) { $id = $id ?: get_current_user_id(); $user = get_user_by('ID', $id); $fullName = $user->display_name; if ($user->first_name && $user->last_name) { $fullName = $user->first_name . ' ' . $user->last_name; } return $fullName; } /** * Get the onboarding settings. * * @return array The onboarding settings. */ public static function getOnboardingSettings() { $default = [ 'is_onboarding_enabled' => 'no', 'registration_page_url' => '', ]; $settings = Utility::getOption('onboarding_settings', $default); return wp_parse_args($settings, $default); } /** * Get all WordPress published pages. * * @return array An array of page data. */ public static function getAllWpPublishedPage() { $posts = get_posts(array( 'post_status' => 'publish', 'numberposts' => -1, 'post_type' => 'any', )); return array_map(function ($post) { return array( 'id' => $post->ID, 'permalink' => get_permalink($post), 'title' => get_the_title($post), ); }, $posts); } /** * Add a user to a space. * * @param Space | int $space space to add the user to. * @param int $userId The ID of the user to add. * @param string $role The role of the user in the space. * @param string $by The source of the action. * @return bool True if the user was added, false otherwise. */ public static function addToSpace($space, $userId, $role = 'member', $by = 'self') { if (is_numeric($space)) { $space = BaseSpace::withoutGlobalScopes()->find($space); } if (!$space) { return false; } $user = User::find($userId); if (!$user) { return false; } $user->syncXProfile(); if($role == 'member' && $space->type == 'course') { $role = 'student'; } $exist = SpaceUserPivot::where('user_id', $userId) ->where('space_id', $space->id) ->first(); if ($exist) { if ($exist->status != 'active') { $exist->status = 'active'; $exist->role = $role; $exist->save(); if ($space->type == 'course') { do_action('fluent_community/course/enrolled', $space, $userId, $by); } else { do_action('fluent_community/space/joined', $space, $userId, $by); } return true; } return false; } SpaceUserPivot::create([ 'space_id' => $space->id, 'role' => $role, 'user_id' => $userId ]); if ($space->type == 'course') { do_action('fluent_community/course/enrolled', $space, $userId, $by); } else { do_action('fluent_community/space/joined', $space, $userId, $by); } return true; } /** * Remove a user from a space if exist. * * @param int $userId The ID of the user. * @param int $spaceId The ID of the space. * @param string $by The source of the action. self | by_admin * @return bool True if the user is in the space, false otherwise. */ public static function removeFromSpace($space, $userId, $by = 'self') { $user = User::find($userId); if (!$user) { return false; } if (is_numeric($space)) { $space = BaseSpace::query()->withoutGlobalScopes()->find($space); } if (!$space) { return false; } if (!self::isUserInSpace($userId, $space->id)) { return false; } SpaceUserPivot::bySpace($space->id) ->byUser($userId) ->delete(); $user->cacheAccessSpaces(); if ($space->type == 'course') { do_action('fluent_community/course/student_left', $space, $userId, $by); } else { do_action('fluent_community/space/user_left', $space, $userId, $by); } return true; } /** * Render a link with icon. * * @param array $link The link data. * @param string $linkClass Additional classes for the link. * @param string $fallback The fallback content if no icon is found. * @param bool $renderIcon Whether to render the icon or not. */ public static function renderLink($link, $linkClass = '', $fallback = '', $renderIcon = true) { $linkAtts = array_filter([ 'class' => trim($linkClass . ' ' . Arr::get($link, 'link_classes')) . ' fcom_compt_link', 'target' => Arr::get($link, 'new_tab') === 'yes' ? '_blank' : '', 'rel' => Arr::get($link, 'new_tab') === 'yes' ? 'noopener noreferrer' : '', ]); ?> $value) { echo esc_attr($key) . '="' . esc_attr($value) . '"'; } ?>> ') { ?>
' . wp_kses_post($fallback) . ''; endif; } /** * Get the IP address of the user. * * @param bool $anonymize Whether to anonymize the IP address. * @return string The IP address. */ public static function getIp($anonymize = false) { static $ipAddress; if ($ipAddress) { return $ipAddress; } if (empty($_SERVER['REMOTE_ADDR'])) { // It's a local cli request return '127.0.0.1'; } $ipAddress = ''; if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { $ipAddress = sanitize_text_field(wp_unslash($_SERVER["REMOTE_ADDR"])); //If it's a valid Cloudflare request if (self::isCfIp($ipAddress)) { //Use the CF-Connecting-IP header. $ipAddress = sanitize_text_field(wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP'])); } } else if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') { // most probably it's local reverse proxy if (isset($_SERVER["HTTP_CLIENT_IP"])) { $ipAddress = sanitize_text_field(wp_unslash($_SERVER["HTTP_CLIENT_IP"])); } else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ipAddress = (string)rest_is_ip_address(trim(current(preg_split('/,/', sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR'])))))); } } if (!$ipAddress) { $ipAddress = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); } $ipAddress = preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ipAddress); $ipAddress = apply_filters('fluent_auth/user_ip', $ipAddress); if ($anonymize) { return wp_privacy_anonymize_ip($ipAddress); } return $ipAddress; } /** * Check if the IP address is from Cloudflare. * * @param string $ip The IP address to check. * @return bool True if the IP is from Cloudflare, false otherwise. */ public static function isCfIp($ip = '') { if (!$ip && isset($_SERVER["REMOTE_ADDR"])) { $ip = sanitize_text_field(wp_unslash($_SERVER["REMOTE_ADDR"])); } if (!$ip) { return false; } $cloudflareIPRanges = array( '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13', '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22', ); //Make sure that the request came via Cloudflare. foreach ($cloudflareIPRanges as $range) { //Use the ip_in_range function from Joomla. if (self::ipInRange($ip, $range)) { //IP is valid. Belongs to Cloudflare. return true; } } return false; } /** * Check if the IP address is in the given range. * * @param string $ip The IP address to check. * @param string $range The range to check against. * @return bool True if the IP is in the range, false otherwise. */ private static function ipInRange($ip, $range) { if (strpos($range, '/') !== false) { // $range is in IP/NETMASK format list($range, $netmask) = explode('/', $range, 2); if (strpos($netmask, '.') !== false) { // $netmask is a 255.255.0.0 format $netmask = str_replace('*', '0', $netmask); $netmask_dec = ip2long($netmask); return ((ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec)); } else { // $netmask is a CIDR size block // fix the range argument $x = explode('.', $range); while (count($x) < 4) $x[] = '0'; list($a, $b, $c, $d) = $x; $range = sprintf("%u.%u.%u.%u", empty($a) ? '0' : $a, empty($b) ? '0' : $b, empty($c) ? '0' : $c, empty($d) ? '0' : $d); $range_dec = ip2long($range); $ip_dec = ip2long($ip); # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0')); # Strategy 2 - Use math to create it $wildcard_dec = pow(2, (32 - $netmask)) - 1; $netmask_dec = ~$wildcard_dec; return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec)); } } else { // range might be 255.255.*.* or 1.2.3.0-1.2.3.255 if (strpos($range, '*') !== false) { // a.b.*.* format // Just convert to A-B format by setting * to 0 for A and 255 for B $lower = str_replace('*', '0', $range); $upper = str_replace('*', '255', $range); $range = "$lower-$upper"; } if (strpos($range, '-') !== false) { // A-B format list($lower, $upper) = explode('-', $range, 2); $lower_dec = (float)sprintf("%u", ip2long($lower)); $upper_dec = (float)sprintf("%u", ip2long($upper)); $ip_dec = (float)sprintf("%u", ip2long($ip)); return (($ip_dec >= $lower_dec) && ($ip_dec <= $upper_dec)); } return false; } } public static function getPortalRequestPath($requestUri) { $portalSlug = self::getPortalSlug(); if ($portalSlug == $requestUri) { return 'portal_home'; } if (!$requestUri) { return false; } if ($portalSlug) { // remove the portal slug from the request uri. Don't use str_replace as it will replace all occurrences $requestUri = substr($requestUri, strlen($portalSlug)); } $parts = explode('/', $requestUri); $start = $parts[0]; $routeStats = self::portalRoutePaths(); if (in_array($start, $routeStats)) { return $requestUri; } return false; } public static function getTopicsConfig() { return Utility::getFromCache('topics_config', function () { $config = Utility::getOption('topics_config', []); $default = [ 'max_topics_per_post' => 1, 'max_topics_per_space' => 20, 'show_on_post_card' => 'yes' ]; return wp_parse_args($config, $default); }, WEEK_IN_SECONDS); } }