| @@ -10,8 +10,9 @@ | ||
| 10 | 10 | use FluentCommunity\App\Services\FeedsHelper; |
| 11 | 11 | use FluentCommunity\App\Services\Helper; |
| 12 | 12 | use FluentCommunity\Framework\Support\Arr; |
| 13 | 13 | use FluentCommunity\Modules\Course\Model\Course; |
| 14 | +use FluentCommunity\Modules\PushNotification\PushNotificationModule; | |
| 14 | 15 | |
| 15 | 16 | class Utility |
| 16 | 17 | { |
| 17 | 18 | public static function isDev() |
| @@ -116,8 +117,9 @@ | ||
| 116 | 117 | 'has_crm_sync' => 'no', |
| 117 | 118 | 'content_moderation' => 'no', |
| 118 | 119 | 'followers_module' => 'no', |
| 119 | 120 | 'custom_profile_fields' => 'no', |
| 121 | + 'pwa_module' => 'no', | |
| 120 | 122 | ]; |
| 121 | 123 | |
| 122 | 124 | if (defined('FLUENT_COMMUNITY_CLOUD_STORAGE') && FLUENT_COMMUNITY_CLOUD_STORAGE) { |
| 123 | 125 | $features['cloud_storage'] = 'yes'; |
| @@ -134,8 +136,9 @@ | ||
| 134 | 136 | $features['cloud_storage'] = 'no'; |
| 135 | 137 | $features['user_badge'] = 'no'; |
| 136 | 138 | $features['followers_module'] = 'no'; |
| 137 | 139 | $features['custom_profile_fields'] = 'no'; |
| 140 | + $features['pwa_module'] = 'no'; | |
| 138 | 141 | } |
| 139 | 142 | |
| 140 | 143 | return $features; |
| 141 | 144 | } |
| @@ -201,8 +204,9 @@ | ||
| 201 | 204 | } |
| 202 | 205 | |
| 203 | 206 | $defaults = [ |
| 204 | 207 | 'dark_mode' => 'yes', |
| 208 | + 'default_theme_mode' => 'light', | |
| 205 | 209 | 'fixed_page_header' => 'yes', |
| 206 | 210 | 'show_powered_by' => 'yes', |
| 207 | 211 | 'feed_link_on_sidebar' => 'yes', |
| 208 | 212 | 'show_post_modal' => 'yes', |
| @@ -216,9 +220,11 @@ | ||
| 216 | 220 | 'post_title_pref' => 'optional', |
| 217 | 221 | 'max_media_per_post' => 4, |
| 218 | 222 | 'disable_feed_sort_by' => 'no', |
| 219 | 223 | 'default_feed_sort_by' => '', |
| 220 | - 'collapse_sidebar_groups' => 'no' | |
| 224 | + 'collapse_sidebar_groups' => 'no', | |
| 225 | + 'hide_header_on_scroll' => 'no', | |
| 226 | + 'enable_sidebar_toggle' => 'no' | |
| 221 | 227 | ]; |
| 222 | 228 | $settings = self::getOption('customization_settings', $defaults); |
| 223 | 229 | |
| 224 | 230 | $settings = wp_parse_args($settings, $defaults); |
| @@ -227,8 +233,9 @@ | ||
| 227 | 233 | $settings['show_powered_by'] = 'yes'; |
| 228 | 234 | $settings['affiliate_id'] = ''; |
| 229 | 235 | $settings['rich_post_layout'] = 'classic'; |
| 230 | 236 | $settings['member_list_layout'] = 'classic'; |
| 237 | + $settings['enable_sidebar_toggle'] = 'no'; | |
| 231 | 238 | } |
| 232 | 239 | return $settings; |
| 233 | 240 | } |
| 234 | 241 | |
| @@ -258,13 +265,15 @@ | ||
| 258 | 265 | |
| 259 | 266 | $defaults = [ |
| 260 | 267 | 'can_customize_username' => 'no', |
| 261 | 268 | 'can_change_email' => 'no', |
| 269 | + 'can_change_password' => 'yes', | |
| 262 | 270 | 'show_last_activity' => 'yes', |
| 263 | 271 | 'can_deactive_account' => 'no', |
| 264 | 272 | 'email_auto_login' => 'yes', |
| 265 | 273 | 'enable_gravatar' => 'yes', |
| 266 | 274 | 'enable_user_sync' => 'yes', |
| 275 | + 'skip_crm_undeliverable_emails' => 'yes', | |
| 267 | 276 | 'members_page_status' => 'everybody', // everybody, logged_in, admin_only |
| 268 | 277 | 'profile_page_visibility' => 'everybody', // everybody, logged_in, admin_only |
| 269 | 278 | 'user_space_visibility' => 'everybody', // everybody, logged_in, admin_only |
| 270 | 279 | 'leaderboard_members_visibility' => 'everybody', // everybody, logged_in, admin_only |
| @@ -340,8 +349,12 @@ | ||
| 340 | 349 | { |
| 341 | 350 | $preSettings = self::getPrivacySettings(); |
| 342 | 351 | $settings = Arr::only($settings, array_keys($preSettings)); |
| 343 | 352 | |
| 353 | + $settings = array_map(function ($value) { | |
| 354 | + return is_scalar($value) ? sanitize_text_field($value) : ''; | |
| 355 | + }, $settings); | |
| 356 | + | |
| 344 | 357 | self::updateOption('privacy_settings', $settings); |
| 345 | 358 | self::forgetCache('privacy_settings'); |
| 346 | 359 | |
| 347 | 360 | return $settings; |
| @@ -385,8 +398,46 @@ | ||
| 385 | 398 | ], $url); |
| 386 | 399 | } |
| 387 | 400 | |
| 388 | 401 | /** |
| 402 | + * Build a spec-compliant "Upgrade to Pro" URL. | |
| 403 | + * | |
| 404 | + * Follows the shared Fluent* UTM spec: | |
| 405 | + * utm_source = fluent-community (fixed vocabulary, never the wp.org slug) | |
| 406 | + * utm_medium = free_plugin | pro_plugin (acquisition vs cross-sell) | |
| 407 | + * utm_campaign= upgrade_pro (override for xsell_<target> / license_* ) | |
| 408 | + * utm_content = the exact placement, e.g. feature_lock_moderation, upgrade_page | |
| 409 | + * utm_term = plugin version that generated the link | |
| 410 | + * utm_id = promo id, blank normally (omit unless passed) | |
| 411 | + * | |
| 412 | + * @param string $content The utm_content placement. | |
| 413 | + * @param array $overrides Override any utm_* param (e.g. utm_campaign for cross-sell). | |
| 414 | + * @return string | |
| 415 | + */ | |
| 416 | + public static function getProUpgradeUrl($content = 'upgrade_page', $overrides = []) | |
| 417 | + { | |
| 418 | + $baseUrl = apply_filters( | |
| 419 | + 'fluent_community/pro_upgrade_base_url', | |
| 420 | + 'https://fluentcommunity.co/pricing/' | |
| 421 | + ); | |
| 422 | + | |
| 423 | + $params = wp_parse_args($overrides, [ | |
| 424 | + 'utm_source' => 'fluent-community', | |
| 425 | + 'utm_medium' => defined('FLUENT_COMMUNITY_PRO_VERSION') ? 'pro_plugin' : 'free_plugin', | |
| 426 | + 'utm_campaign' => 'upgrade_pro', | |
| 427 | + 'utm_content' => $content, | |
| 428 | + 'utm_term' => FLUENT_COMMUNITY_PLUGIN_VERSION, | |
| 429 | + ]); | |
| 430 | + | |
| 431 | + // Drop any blank params (e.g. an unset utm_id) so they never hit the URL. | |
| 432 | + $params = array_filter($params, function ($value) { | |
| 433 | + return $value !== '' && $value !== null; | |
| 434 | + }); | |
| 435 | + | |
| 436 | + return add_query_arg($params, $baseUrl); | |
| 437 | + } | |
| 438 | + | |
| 439 | + /** | |
| 389 | 440 | * Get the email notification settings. |
| 390 | 441 | * |
| 391 | 442 | * @return array The email notification settings. |
| 392 | 443 | */ |
| @@ -426,8 +477,29 @@ | ||
| 426 | 477 | |
| 427 | 478 | return $settings; |
| 428 | 479 | } |
| 429 | 480 | |
| 481 | + public static function getPushNotificationSettings() | |
| 482 | + { | |
| 483 | + static $settings; | |
| 484 | + if ($settings) return $settings; | |
| 485 | + | |
| 486 | + $default = [ | |
| 487 | + 'push_enabled' => 'yes', | |
| 488 | + 'com_my_post_push' => 'yes', | |
| 489 | + 'reply_my_com_push' => 'yes', | |
| 490 | + 'mention_push' => 'yes', | |
| 491 | + 'co_com_push' => 'yes', | |
| 492 | + 'prompt_placements' => PushNotificationModule::PROMPT_PLACEMENTS | |
| 493 | + ]; | |
| 494 | + | |
| 495 | + $settings = self::getOption('global_push_settings', $default); | |
| 496 | + | |
| 497 | + $settings = wp_parse_args($settings, $default); | |
| 498 | + | |
| 499 | + return $settings; | |
| 500 | + } | |
| 501 | + | |
| 430 | 502 | public static function hasEmailAnnouncementEnabled() |
| 431 | 503 | { |
| 432 | 504 | $settings = self::getEmailNotificationSettings(); |
| 433 | 505 | return Arr::get($settings, 'mention_mail', 'no') === 'yes'; |
| @@ -478,8 +550,25 @@ | ||
| 478 | 550 | $key = 'fluent_community_post_topics'; |
| 479 | 551 | $topics = self::getFromCache($key, function () { |
| 480 | 552 | $topics = Term::where('taxonomy_name', 'post_topic')->orderBy('title', 'ASC')->get(); |
| 481 | 553 | |
| 554 | + // Respect the admin-defined order (settings.serial) when present; | |
| 555 | + // topics without a serial fall back to the alphabetical order above. | |
| 556 | + $topics = $topics->sort(function ($a, $b) { | |
| 557 | + $serialA = Arr::get($a->settings, 'serial'); | |
| 558 | + $serialB = Arr::get($b->settings, 'serial'); | |
| 559 | + if ($serialA === null && $serialB === null) { | |
| 560 | + return 0; | |
| 561 | + } | |
| 562 | + if ($serialA === null) { | |
| 563 | + return 1; | |
| 564 | + } | |
| 565 | + if ($serialB === null) { | |
| 566 | + return -1; | |
| 567 | + } | |
| 568 | + return (int) $serialA <=> (int) $serialB; | |
| 569 | + })->values(); | |
| 570 | + | |
| 482 | 571 | /* |
| 483 | 572 | * object_id = term_id |
| 484 | 573 | * meta_key = space_id |
| 485 | 574 | */ |
| @@ -502,8 +591,9 @@ | ||
| 502 | 591 | 'title' => $topic->title, |
| 503 | 592 | 'description' => $topic->description, |
| 504 | 593 | 'slug' => $topic->slug, |
| 505 | 594 | 'admin_only' => Arr::get($topic->settings, 'admin_only', 'no'), |
| 595 | + 'serial' => Arr::get($topic->settings, 'serial'), | |
| 506 | 596 | 'space_ids' => isset($relations[$topic->id]) ? $relations[$topic->id] : [] |
| 507 | 597 | ]; |
| 508 | 598 | } |
| 509 | 599 | |
| @@ -1072,14 +1162,28 @@ | ||
| 1072 | 1162 | $darkCss = self::generateCss(Arr::get($schemas, "darkSkins.$darkName.selectors"), 'html.dark'); |
| 1073 | 1163 | return $lightCss . $darkCss; |
| 1074 | 1164 | } |
| 1075 | 1165 | |
| 1076 | - public static function getThemeColor() | |
| 1166 | + public static function getThemeColor($scope = 'theme') | |
| 1077 | 1167 | { |
| 1168 | + static $cache = []; | |
| 1169 | + if (isset($cache[$scope])) { | |
| 1170 | + return $cache[$scope]; | |
| 1171 | + } | |
| 1172 | + | |
| 1173 | + $map = [ | |
| 1174 | + 'theme' => ['key' => 'primary_button', 'default' => '#2B2E33'], | |
| 1175 | + 'theme_button_text' => ['key' => 'primary_button_text', 'default' => '#ffffff'], | |
| 1176 | + ]; | |
| 1177 | + $entry = Arr::get($map, $scope, ['key' => $scope, 'default' => '']); | |
| 1178 | + | |
| 1078 | 1179 | $schemas = self::getColorSchemas(); |
| 1079 | 1180 | $lightName = Arr::get(self::getColorConfig('view'), 'light_schema', 'default'); |
| 1181 | + $color = Arr::get($schemas, "lightSkins.$lightName.selectors.body.{$entry['key']}", $entry['default']); | |
| 1080 | 1182 | |
| 1081 | - return Arr::get($schemas, "lightSkins.$lightName.selectors.body.primary_button", '#2B2E33'); | |
| 1183 | + $cache[$scope] = apply_filters('fluent_community/' . $scope . '_color', $color); | |
| 1184 | + | |
| 1185 | + return $cache[$scope]; | |
| 1082 | 1186 | } |
| 1083 | 1187 | |
| 1084 | 1188 | public static function getColorSchemaConfig() |
| 1085 | 1189 | { |
| @@ -1135,8 +1239,11 @@ | ||
| 1135 | 1239 | |
| 1136 | 1240 | public static function slugify($text, $fallback = '') |
| 1137 | 1241 | { |
| 1138 | 1242 | $title = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $text); |
| 1243 | + | |
| 1244 | + $title = Helper::normalizeToAscii($title); | |
| 1245 | + | |
| 1139 | 1246 | $title = remove_accents($title); |
| 1140 | 1247 | |
| 1141 | 1248 | $title = strtolower($title); |
| 1142 | 1249 | // only allow alphanumeric, dash, and underscore |