mayBeRedirect(); add_action('admin_enqueue_scripts', array($this, 'loadAssets'), 1); } } public function addMenu() { $permissions = PermissionManager::currentUserPermissions(); if (!$permissions) { return; } $isAdmin = false; if (in_array('administrator', $permissions)) { $dashboardPermission = 'manage_options'; $isAdmin = true; } else { $user = get_user_by('ID', get_current_user_id()); $roles = array_values((array)$user->roles); $dashboardPermission = Arr::get($roles, 0); } $title = __('FluentCRM', 'fluent-crm'); if (defined('FLUENTCAMPAIGN')) { $title = __('FluentCRM Pro', 'fluent-crm'); } add_menu_page( $title, $title, $dashboardPermission, 'fluentcrm-admin', array($this, 'render'), $this->getMenuIcon(), 2 ); add_submenu_page( 'fluentcrm-admin', __('Dashboard', 'fluent-crm'), __('Dashboard', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin', array($this, 'render') ); if (in_array('fcrm_read_contacts', $permissions)) { add_submenu_page( 'fluentcrm-admin', __('Contacts', 'fluent-crm'), __('Contacts', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/subscribers', array($this, 'render') ); if (in_array('fcrm_manage_contact_cats', $permissions)) { if (Helper::isCompanyEnabled()) { add_submenu_page( 'fluentcrm-admin', __('Companies', 'fluent-crm'), __('Companies', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/contact-groups/companies', array($this, 'render') ); } add_submenu_page( 'fluentcrm-admin', __('Lists', 'fluent-crm'), __('Lists', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/contact-groups/lists', array($this, 'render') ); add_submenu_page( 'fluentcrm-admin', __('Tags', 'fluent-crm'), __('Tags', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/contact-groups/tags', array($this, 'render') ); } } if (in_array('fcrm_read_emails', $permissions)) { add_submenu_page( 'fluentcrm-admin', __('Campaigns', 'fluent-crm'), __('Campaigns', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/email/campaigns', array($this, 'render') ); add_submenu_page( 'fluentcrm-admin', __('Recurring Campaigns', 'fluent-crm'), __('Recurring Campaigns', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/email/recurring-campaigns', array($this, 'render') ); add_submenu_page( 'fluentcrm-admin', __('Email Sequences', 'fluent-crm'), __('Email Sequences', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/email/sequences', array($this, 'render') ); add_submenu_page( 'fluentcrm-admin', __('Email Templates', 'fluent-crm'), __('Email Templates', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/email/templates', array($this, 'render') ); } if (in_array('fcrm_manage_forms', $permissions)) { add_submenu_page( 'fluentcrm-admin', __('Forms', 'fluent-crm'), __('Forms', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/forms', array($this, 'render') ); } if (in_array('fcrm_read_funnels', $permissions)) { add_submenu_page( 'fluentcrm-admin', __('Automations', 'fluent-crm'), __('Automations', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/funnels', array($this, 'render') ); } do_action('fluent_crm/after_core_menu_items', $permissions, $isAdmin); if (in_array('fcrm_manage_settings', $permissions)) { add_submenu_page( 'fluentcrm-admin', __('Settings', 'fluent-crm'), __('Settings', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/settings', array($this, 'render') ); add_submenu_page( 'fluentcrm-admin', __('Reports', 'fluent-crm'), __('Reports', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/reports', array($this, 'render') ); add_submenu_page( 'fluentcrm-admin', __('Addons', 'fluent-crm'), __('Addons', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/add-ons', array($this, 'render') ); } if (!defined('FLUENTMAIL_PLUGIN_VERSION')) { add_submenu_page( 'fluentcrm-admin', __('SMTP', 'fluent-crm'), __('SMTP', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/settings/smtp_settings', array($this, 'render') ); } if (in_array('fcrm_view_dashboard', $permissions)) { add_submenu_page( 'fluentcrm-admin', __('Help', 'fluent-crm'), __('Help', 'fluent-crm'), $dashboardPermission, 'fluentcrm-admin#/documentation', array($this, 'render') ); } } public function render() { $this->changeFooter(); $app = FluentCrm(); /** * FluentCRM Admin App Loading Hook */ do_action('fluentcrm_loading_app'); wp_enqueue_script( 'fluentcrm_admin_app_start', fluentCrmMix('/admin/js/app.js'), array('fluentcrm_admin_app_boot'), $this->version ); /** * Controls whether the FluentCRM admin top menu bar should be rendered. * * @param bool $renderTopMenuBar Whether to render the top menu bar. */ $renderTopMenuBar = apply_filters('fluent_crm/render_top_menu_bar', true); $urlBase = fluentcrm_menu_url_base(); $menuItems = $this->getMenuItems($urlBase); $proData = [ 'label' => __('Upgrade to Pro', 'fluent-crm'), 'permalink' => 'https://fluentcrm.com?utm_source=dashboard&utm_medium=plugin&utm_campaign=pro&utm_id=wp', 'has_pro' => defined('FLUENTCAMPAIGN') ]; $app['view']->render('admin.new_menu_page', [ 'menuItems' => $menuItems, 'settingsUrl' => $urlBase . 'settings', 'logo' => FLUENTCRM_PLUGIN_URL . 'assets/images/fluentcrm-logo.svg', 'base_url' => $urlBase, 'proData' => $proData, 'renderTopMenuBar' => $renderTopMenuBar ]); } public function changeFooter() { add_filter('admin_footer_text', function ($content) { $url = 'https://fluentcrm.com'; $extraHtml = ''; if (!defined('DISABLE_WP_CRON')) { $doc_url = 'https://fluentcrm.com/docs/fluentcrm-cron-job-basics-and-checklist/'; $extraHtml = ' ' . sprintf( wp_kses( /* translators: %1$s: Opening tag linking to FluentCRM cron job docs. %2$s: Closing tag. */ __('Server-Side Cron Job is not enabled %1$sView Documentation%2$s.', 'fluent-crm'), array( 'a' => array( 'href' => array(), 'target' => array(), 'rel' => array(), 'style' => array(), ) ) ), '', '' ); } /* translators: %s: the FluentCRM website URL (used in the href of the link) */ return sprintf(wp_kses(__('Thank you for using FluentCRM.', 'fluent-crm'), array('a' => array('href' => array()))), esc_url($url)) . '. ' . $extraHtml; }); add_filter('update_footer', function ($text) { if (defined('FLUENTCAMPAIGN_PLUGIN_VERSION') && FLUENTCRM_PLUGIN_VERSION != FLUENTCAMPAIGN_PLUGIN_VERSION) { return FLUENTCRM_PLUGIN_VERSION . ' & ' . FLUENTCAMPAIGN_PLUGIN_VERSION; } return FLUENTCRM_PLUGIN_VERSION; }); } public function getMenuItems($urlBase = null) { if (!$urlBase) { $urlBase = fluentcrm_menu_url_base(); } $permissions = PermissionManager::currentUserPermissions(); $menuItems = [ [ 'key' => 'dashboard', 'label' => __('Dashboard', 'fluent-crm'), 'permalink' => $urlBase ] ]; if (in_array('fcrm_read_contacts', $permissions)) { $contactMenu = [ 'key' => 'contacts', 'label' => __('Contacts', 'fluent-crm'), 'permalink' => $urlBase . 'subscribers', 'layout_class' => 'fc_1_col_menu', 'sub_items' => [ [ 'key' => 'all_contacts', 'label' => __('All Contacts', 'fluent-crm'), 'permalink' => $urlBase . 'subscribers', 'description' => __('Browse all your subscribers and customers', 'fluent-crm'), 'icon' => ' ' ] ] ]; if (in_array('fcrm_manage_contact_cats', $permissions)) { $contactMenu['sub_items'][] = [ 'key' => 'lists', 'label' => __('Lists', 'fluent-crm'), 'permalink' => $urlBase . 'contact-groups/lists', 'description' => __('Browse and Manage your lists associate with contact', 'fluent-crm'), 'icon' => ' ' ]; $contactMenu['sub_items'][] = [ 'key' => 'tags', 'label' => __('Tags', 'fluent-crm'), 'permalink' => $urlBase . 'contact-groups/tags', 'description' => __('Browse and Manage your tags associate with contact', 'fluent-crm'), 'icon' => ' ' ]; if (Helper::isCompanyEnabled()) { $contactMenu['sub_items'][] = [ 'key' => 'companies', 'label' => __('Companies', 'fluent-crm'), 'permalink' => $urlBase . 'contact-groups/companies', 'description' => __('Browse and Manage contact business/companies', 'fluent-crm'), 'icon' => ' ' ]; } $contactMenu['sub_items'][] = [ 'key' => 'dynamic_segments', 'label' => __('Segments', 'fluent-crm'), 'permalink' => $urlBase . 'contact-groups/dynamic-segments', 'description' => __('Manage your dynamic contact segments', 'fluent-crm'), 'icon' => ' ' ]; } $menuItems[] = $contactMenu; } if (in_array('fcrm_read_emails', $permissions)) { $campaignMenu = [ 'key' => 'campaigns', 'label' => __('Emails', 'fluent-crm'), 'permalink' => $urlBase . 'email/campaigns', 'layout_class' => 'fc_1_col_menu' ]; $campaignMenu['sub_items'] = [ [ 'key' => 'all_campaigns', 'label' => __('Campaigns', 'fluent-crm'), 'permalink' => $urlBase . 'email/campaigns', 'description' => __('Send Email Broadcast to your selected subscribers by tags, lists or segment', 'fluent-crm'), 'icon' => ' ' ], [ 'key' => 'recurring_campaigns', 'label' => __('Recurring Campaigns', 'fluent-crm'), 'permalink' => $urlBase . 'email/recurring-campaigns', 'description' => __('Send automated daily or weekly emails of your dynamic data like new blog posts', 'fluent-crm'), 'icon' => ' ' ], [ 'key' => 'email_sequences', 'label' => __('Sequences', 'fluent-crm'), 'permalink' => $urlBase . 'email/sequences', 'description' => __('Create Multiple Emails and Send in order as a Drip Email Campaign', 'fluent-crm'), 'icon' => ' ' ], [ 'key' => 'email_templates', 'label' => __('Templates', 'fluent-crm'), 'permalink' => $urlBase . 'email/templates', 'description' => __('Create email templates to use as a starting point in your emails', 'fluent-crm'), 'icon' => ' ' ], [ 'key' => 'email_patterns', 'label' => __('Patterns', 'fluent-crm'), 'permalink' => $urlBase . 'email/patterns', 'description' => __('Create reusable email sections to use across your campaigns and templates', 'fluent-crm'), 'icon' => ' ' ], [ 'key' => 'all_emails', 'label' => __('All Activities', 'fluent-crm'), 'permalink' => $urlBase . 'email/all-emails', 'description' => __('Find all the emails that are being sent or scheduled by FluentCRM', 'fluent-crm'), 'icon' => ' ' ] ]; $menuItems[] = $campaignMenu; } if (in_array('fcrm_manage_forms', $permissions)) { $menuItems[] = [ 'key' => 'forms', 'label' => __('Forms', 'fluent-crm'), 'permalink' => $urlBase . 'forms' ]; } if (in_array('fcrm_read_funnels', $permissions)) { $menuItems[] = [ 'key' => 'funnels', 'label' => __('Automations', 'fluent-crm'), 'permalink' => $urlBase . 'funnels' ]; } /** * Filters the core menu items for FluentCRM. * * This filter allows modification of the core menu items in the FluentCRM admin menu. * * @param array $menuItems The current menu items. * @param array $permissions The permissions associated with the menu items. * @return array The filtered menu items. */ $menuItems = apply_filters('fluent_crm/core_menu_items', $menuItems, $permissions, $urlBase); if (in_array('fcrm_manage_settings', $permissions)) { $menuItems[] = [ 'key' => 'reports', 'label' => __('Reports', 'fluent-crm'), 'permalink' => $urlBase . 'reports', 'layout_class' => 'fc_1_col_menu', 'sub_items' => [ [ 'key' => 'reports_contacts', 'label' => __('Contacts', 'fluent-crm'), 'permalink' => $urlBase . 'reports?tab=contacts', 'icon' => ' ' ], [ 'key' => 'reports_emails', 'label' => __('Emails', 'fluent-crm'), 'permalink' => $urlBase . 'reports?tab=emails', 'icon' => '', ], ], ]; $menuItems[] = [ 'key' => 'settings', 'label' => __('Settings', 'fluent-crm'), 'permalink' => $urlBase . 'settings' ]; } /** * Filter the menu items for FluentCRM. * * This filter allows modification of the menu items in the FluentCRM admin menu. * * @param array $menuItems An array of menu items for FluentCRM. * @return array The filtered array of menu items. */ return apply_filters('fluent_crm/menu_items', $menuItems); } public function mayBeRedirect() { if (fluentcrm_get_option('fluentcrm_setup_wizard_ran') != 'yes' && !isset($_GET['setup_complete'])) { if (current_user_can('manage_options')) { wp_safe_redirect(admin_url('index.php?page=fluentcrm-setup')); exit(); } } } public function loadAssets() { if (!isset($_GET['page']) || $_GET['page'] != 'fluentcrm-admin') { return; } /* * LearnPress loads all their JS weired way on every Admin Pages */ if (defined('LEARNPRESS_VERSION')) { add_filter('learn-press/admin-default-scripts', function ($scripts) { return []; }); } $this->loadCssJs(); } public function loadCssJs() { $this->unloadOtherScripts(); // Inject Vite client for HMR in development mode add_action('admin_head', function () { \FluentCrm\App\Vite::injectViteClient(); }, 1); // NOTE: HTML-level hints were attempted // and reverted. Hosting layers that fingerprint asset URLs (the // 3.0.5 trigger) would rewrite the preload hrefs but NOT the // chunk-to-chunk import URLs Vite bakes at build time — leading // to URL divergence and module duplication at the chunk level // (the same root cause as 3.0.5, one level down the chunk tree). // Route preloading now happens via Vite-baked dynamic import() // calls in resources/admin/app.js's preload callback, which // guarantees URL parity. See docs/app-entry-refactor-roadmap.md. wp_enqueue_script('fluentcrm_global_admin', fluentCrmMix('admin/js/global_admin.js'), array('jquery'), $this->version); wp_enqueue_script('fluentcrm_admin_app_boot', fluentCrmMix('admin/js/boot.js'), array(), $this->version); // Ensure block editor styles are loaded // wp_enqueue_style('wp-block-editor'); // may not needed as we are using editor styles in the react app $this->emailBuilderBlockInit(); /** * Action Hook when global admin scripts are loaded */ do_action('fluent_crm/global_app_boot_loaded'); $footerHook = 'admin_footer'; if (!is_admin()) { $footerHook = 'wp_footer'; } add_action($footerHook, function () { ?> loadCss(); wp_enqueue_script('dompurify', fluentCrmMix('libs/purify/purify.min.js'), [], $this->version, true); $inlineCss = Helper::generateThemePrefCss(); wp_add_inline_style('fluentcrm_app_global', $inlineCss); remove_action('admin_print_scripts', 'print_emoji_detection_script'); add_filter('tiny_mce_plugins', function ($plugins) { if (is_array($plugins)) { return array_diff($plugins, array('wpemoji')); } return array(); }); wp_localize_script('fluentcrm_admin_app_boot', 'fcAdmin', $this->getAdminVars()); } public function getAdminVars() { $app = FluentCrm(); $tags = Tag::orderBy('title', 'ASC')->get(); $formattedTags = []; foreach ($tags as $tag) { $formattedTags[] = [ 'id' => strval($tag->id), 'title' => $tag->title, 'slug' => $tag->slug ]; } $lists = Lists::orderBy('title', 'ASC')->get(); $formattedLists = []; foreach ($lists as $list) { $formattedLists[] = [ 'id' => strval($list->id), 'title' => $list->title, 'slug' => $list->slug ]; } $currentUser = wp_get_current_user(); $activatedFeatures = Helper::getActivatedFeatures(); $postTypes = get_post_types(['public' => true], 'objects'); unset($postTypes['attachment']); $formattedPostTypes = []; foreach ($postTypes as $postTypeName => $postType) { $formattedPostTypes[] = [ 'id' => $postTypeName, 'title' => $postType->label ]; } $blockEditorUrl = site_url('?fluent_crm_block_editor=1'); if (current_user_can('edit_posts')) { $blockEditorUrl = admin_url('post-new.php?fluent_crm_block_editor=1'); } $existingSettings = get_option('fluentcrm-global-settings'); $data = array( 'business_settings' => [ 'business_name' => Arr::get($existingSettings, 'business_settings.business_name'), 'business_email' => Arr::get($existingSettings, 'business_settings.admin_email'), 'business_address' => Arr::get($existingSettings, 'business_settings.business_address') ], 'images_url' => fluentCrmMix('images'), 'ajaxurl' => admin_url('admin-ajax.php'), 'ajax_nonce' => wp_create_nonce('fluentcrm_ajax_nonce'), 'admin_url' => admin_url('admin.php?page=fluentcrm-admin#/'), 'site_url' => site_url('/'), 'slug' => FLUENTCRM, 'rest' => $this->getRestInfo($app), /** * Filters the list of countries in FluentCRM. * * This filter allows you to modify the list of countries used in the application. * * @param array An array of countries. */ 'countries' => apply_filters('fluent_crm/countries', []), 'contact_types' => fluentcrm_contact_types(), 'purchase_providers' => Helper::getPurchaseHistoryProviders(), /** * Filters the form submission providers in FluentCRM. * * This filter allows you to modify the list of form submission providers. * * @param array An array of form submission providers. */ 'form_submission_providers' => apply_filters('fluent_crm/form_submission_providers', []), /** * Filters the list of support ticket providers in FluentCRM. * * This filter allows you to modify the array of support ticket providers. * * @param array An array of support ticket providers. */ 'support_tickets_providers' => apply_filters('fluentcrm-support_tickets_providers', []), 'activity_types' => fluentcrm_activity_types(), 'profile_sections' => Helper::getProfileSections(), 'globalSmartCodes' => Helper::getGlobalSmartCodes(), 'extendedSmartCodes' => Helper::getExtendedSmartCodes(), 'addons' => $activatedFeatures, 'email_template_designs' => Helper::getEmailDesignTemplates(), 'contact_prefixes' => Helper::getContactPrefixes(), 'contact_custom_fields' => fluentcrm_get_custom_contact_fields(), 'server_time' => current_time('mysql'), // Only users who can manage settings can hit the repair endpoint // (SettingsPolicy → fcrm_manage_settings), so only they get a real // health signal. Everyone else gets `true` — and the health check is // skipped via short-circuit — so the SPA never fires a repair the // server would just reject. For permitted users this is a cheap // cached read (one fc_meta option) on the happy path; when it is // false, app.js fires a one-off background repair on boot. 'db_index_health_ok' => !PermissionManager::currentUserCan('fcrm_manage_settings') || !DbPerformanceService::hasBrokenIndex(false), 'crm_pro_url' => 'https://fluentcrm.com/?utm_source=plugin&utm_medium=admin&utm_campaign=promo', /** * Determine if request verification is required in FluentCRM. * * This filter allows you to specify whether request verification is required. * By default, it is set to false. * * @param bool Whether request verification is required. Default false. */ 'require_verify_request' => apply_filters('fluentcrm_is_require_verify', false), 'trans' => TransStrings::getStrings(), 'has_fluentsmtp' => defined('FLUENTMAIL'), /** * Determine if FluentMail suggestion should be disabled in FluentCRM. * * This filter allows customization of the FluentMail suggestion feature in FluentCRM. * * @return bool True if FluentMail suggestion is disabled, false otherwise. */ 'disable_fluentmail_suggest' => apply_filters('fluent_crm/fluentmail_suggest', defined('FLUENTMAIL')), 'verified_senders' => $this->getVerifiedSenders(), 'has_smart_link' => $this->hasSmartLink(), 'auth' => [ 'permissions' => PermissionManager::currentUserPermissions(), 'first_name' => $currentUser->first_name, 'last_name' => $currentUser->last_name, 'email' => $currentUser->user_email, 'avatar' => fluentcrmGetAvatarHtml($currentUser->user_email, $currentUser->display_name, 128), 'user_id' => $currentUser->ID ], 'is_rtl' => fluentcrm_is_rtl(), 'icons' => [ 'trigger_icon' => 'fc-icon-trigger', ], /** * Define the funnel category icons in FluentCRM. * * This filter allows you to change the icons used for different funnel categories in FluentCRM. * * @param array An associative array where the keys are funnel categories and values are arrays with: * - 'svg' => (string) Inline SVG markup (highest priority) * - 'icon' => (string) CSS icon class name (fallback) */ 'funnel_cat_icons' => apply_filters('fluent_crm/funnel_icons', [ 'crm' => ['svg' => $this->getFunnelCatSvgIcon('crm')], 'wordpresstriggers' => ['svg' => $this->getFunnelCatSvgIcon('wordpresstriggers')], 'woocommerce' => ['svg' => $this->getFunnelCatSvgIcon('woocommerce')], 'lifterlms' => ['svg' => $this->getFunnelCatSvgIcon('lifterlms')], 'easydigitaldownloads' => ['icon' => 'fc-icon-edd'], 'learndash' => ['svg' => $this->getFunnelCatSvgIcon('learndash')], 'memberpress' => ['svg' => $this->getFunnelCatSvgIcon('memberpress')], // Keep both keys for backward compatibility with existing filter integrations. 'paidmembershippro' => ['svg' => $this->getFunnelCatSvgIcon('paidmembershipspro')], 'paidmembershipspro' => ['svg' => $this->getFunnelCatSvgIcon('paidmembershipspro')], 'restrictcontentpro' => ['icon' => 'fc-icon-restric_content'], 'tutorlms' => ['svg' => $this->getFunnelCatSvgIcon('tutorlms')], 'wishlistmember' => ['svg' => $this->getFunnelCatSvgIcon('wishlistmember')], 'surecart' => ['svg' => $this->getFunnelCatSvgIcon('surecart')], 'fluentforms' => ['svg' => $this->getFunnelCatSvgIcon('fluentforms')], 'fluentboards' => ['svg' => $this->getFunnelCatSvgIcon('fluentboards')], 'community' => ['svg' => $this->getFunnelCatSvgIcon('community')], ]), 'advanced_filter_options' => Helper::getAdvancedFilterOptions(), /** * Modify the advanced filter suggestions in FluentCRM. * * This filter allows you to modify the suggestions provided for the advanced filter. * @return array Modified array of suggestions for the advanced filter. */ 'advanced_filter_suggestions' => apply_filters('fluentcrm_advanced_filter_suggestions', []), /** * Define the commerce provider in FluentCRM. * * This filter allows you to change the commerce provider used in FluentCRM. * * @param string The current commerce provider. Default is an empty string. */ 'commerce_provider' => apply_filters('fluentcrm_commerce_provider', ''), /** * Define the currency sign used in FluentCRM. * * This filter allows you to change the currency sign used in the FluentCRM plugin. * * @param string The current currency sign. Default is an empty string. */ 'commerce_currency_sign' => apply_filters('fluentcrm_currency_sign', ''), 'disable_time_diff' => Helper::isExperimentalEnabled('classic_date_time'), 'wp_date_time_format' => $this->getDefaultDateTimeFormatForMoment(), 'disable_ai' => Helper::isExperimentalEnabled('disable_visual_ai'), 'app_version' => FLUENTCRM_PLUGIN_VERSION, 'available_tags' => $formattedTags, 'available_lists' => $formattedLists, 'available_funnel_label_colors' => Helper::funnelLabelColors(), 'available_contact_statuses' => fluentcrm_subscriber_statuses(true), 'available_contact_editable_statuses' => fluentcrm_subscriber_editable_statuses(true), 'available_sms_statuses' => fluentcrm_subscriber_sms_statuses(true), 'available_contact_types' => fluentcrm_contact_types(true), 'available_custom_fields' => fluentcrm_get_option('contact_custom_fields', []), 'contact_sample_csv' => fluentCrmMix('sample.csv'), 'global_email_footer' => Helper::getEmailFooterContent(), 'experimentals' => Helper::getExperimentalSettings(), 'publicPostTypes' => $formattedPostTypes, 'has_woo' => defined('WC_PLUGIN_FILE'), 'debugs' => [ '_fc_last_automation_processor' => get_option('_fc_last_funnel_processor_ran'), '_fcrm_last_scheduler' => fluentCrmGetOptionCache('_fcrm_last_scheduler'), '_fcrm_last_scheduler_for_sms' => fluentCrmGetOptionCache('_fcrm_last_scheduler_for_sms'), ], /** * Determine the custom contact bulk actions in FluentCRM. * * This filter allows you to add or modify the bulk actions available for contacts in the FluentCRM admin interface. * * @param array An array of custom bulk actions for contacts. */ 'custom_contact_bulk_actions' => apply_filters('fluent_crm/custom_contact_bulk_actions', []), 'crm_editor_frame' => $blockEditorUrl, ); if (Arr::get($activatedFeatures, 'company_module')) { $data['company_categories'] = Helper::companyCategories(); $data['company_types'] = Helper::companyTypes(); $data['company_profile_sections'] = Helper::getCompanyProfileSections(); $data['company_custom_fields'] = fluentcrm_get_custom_company_fields(); } /** * Filter the admin variables for FluentCRM. * * This filter allows modification of the admin variables used in FluentCRM. * * @param array $data The array of admin variables. * @return array The filtered array of admin variables. */ return apply_filters('fluent_crm/admin_vars', $data); } public function loadCss() { $isRtl = fluentcrm_is_rtl(); $v3Css = 'admin/css/app3.css'; $appGlobalCss = 'admin/css/app_global.css'; $vendorStyle = 'admin/css/style.css'; if ($isRtl) { // Keep loading the base bundles in RTL mode; dedicated *-rtl build files // are not produced in this Vite pipeline. RTL-specific tweaks live in // admin_rtl.css and are loaded as an additive override below. // $appGlobalCss = 'admin/css/app_global-rtl.css'; // TODO:: The CSS file does not exist. wp_enqueue_style('fluentcrm_app_rtl', fluentCrmMix('admin/css/admin_rtl.css'), [], $this->version); // style.css works for both LTR and RTL — Element Plus handles RTL natively via dir="rtl" } // Legacy fluentcrm-admin.css eliminated — all styles now in app3.css. // Register empty handle for backward compatibility (add-ons may depend on it). wp_register_style('fluentcrm_admin_app', false); wp_enqueue_style('fluentcrm_admin_app'); wp_enqueue_style('fluentcrm_app_global', fluentCrmMix($appGlobalCss), array(), $this->version); wp_enqueue_style('fluentcrm_admin_app1', fluentCrmMix($v3Css), array(), $this->version); // style.css is a build-only artifact (merged Vue/Element Plus chunk CSS). // In dev mode, Vite injects this CSS via HMR automatically. // Element Plus CSS is wrapped in @layer, so overrides always win regardless of load order. if (!\FluentCrm\App\Vite::underDevelopment()) { wp_enqueue_style('fluentcrm_vendor', fluentCrmMix($vendorStyle), array(), $this->version); } } protected function getRestInfo($app) { $ns = $app->config->get('app.rest_namespace'); $v = $app->config->get('app.rest_version'); $restUrl = rest_url($ns . '/' . $v); $restUrl = rtrim($restUrl, '/\\'); return [ 'base_url' => esc_url_raw(rest_url()), 'url' => $restUrl, 'nonce' => wp_create_nonce('wp_rest'), 'namespace' => $ns, 'version' => $v, ]; } public function emailBuilderBlockInit() { if (function_exists('wp_enqueue_media')) { // Editor default styles. add_filter('user_can_richedit', '__return_true'); wp_tinymce_inline_scripts(); wp_enqueue_editor(); wp_enqueue_media(); } } private function getMenuIcon() { return 'data:image/svg+xml;base64,' . base64_encode(''); } private function getVerifiedSenders() { $verifiedSenders = []; if (defined('FLUENTMAIL')) { $smtpSettings = get_option('fluentmail-settings', []); $mappings = (array) Arr::get($smtpSettings, 'mappings', []); if ($mappings) { $verifiedSenders = array_keys($mappings); } } /** * Determine the list of verified email senders in FluentCRM. * * This filter allows modification of the array of verified email senders. * * @param array $verifiedSenders An array of verified email senders. * @return array Filtered array of verified email senders. */ return apply_filters('fluent_crm/verfied_email_senders', $verifiedSenders); } private function hasSmartLink() { if (!defined('FLUENTCAMPAIGN')) { return false; } global $wpdb; $table_name = $wpdb->prefix . 'fc_smart_links'; $query = $wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table_name)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ($wpdb->get_var($query) == $table_name) { return true; } return false; } private function getDefaultDateTimeFormatForMoment() { $phpFormat = get_option('date_format') . ' ' . get_option('time_format'); $replacements = [ 'A' => 'A', // for the sake of escaping below 'a' => 'a', // for the sake of escaping below 'B' => '', // Swatch internet time (.beats), no equivalent 'c' => 'YYYY-MM-DD[T]HH:mm:ssZ', // ISO 8601 'D' => 'ddd', 'd' => 'DD', 'e' => 'zz', // deprecated since version 1.6.0 of moment.js 'F' => 'MMMM', 'G' => 'H', 'g' => 'h', 'H' => 'HH', 'h' => 'hh', 'I' => '', // Daylight Saving Time? => moment().isDST(); 'i' => 'mm', 'j' => 'D', 'L' => '', // Leap year? => moment().isLeapYear(); 'l' => 'dddd', 'M' => 'MMM', 'm' => 'MM', 'N' => 'E', 'n' => 'M', 'O' => 'ZZ', 'o' => 'YYYY', 'P' => 'Z', 'r' => 'ddd, DD MMM YYYY HH:mm:ss ZZ', // RFC 2822 'S' => 'o', 's' => 'ss', 'T' => 'z', // deprecated since version 1.6.0 of moment.js 't' => '', // days in the month => moment().daysInMonth(); 'U' => 'X', 'u' => 'SSSSSS', // microseconds 'v' => 'SSS', // milliseconds (from PHP 7.0.0) 'W' => 'W', // for the sake of escaping below 'w' => 'e', 'Y' => 'YYYY', 'y' => 'YY', 'Z' => '', // time zone offset in minutes => moment().zone(); 'z' => 'DDD', ]; // Converts escaped characters. foreach ($replacements as $from => $to) { $replacements['\\' . $from] = '[' . $from . ']'; } $format = strtr($phpFormat, $replacements); /** * Determine the date and time format used in FluentCRM. * * This filter allows you to modify the date and time format used in FluentCRM. * * @param string $format The current date and time format. * @return string The modified date and time format. */ return apply_filters('fluent_crm/moment_date_time_format', $format); } private function unloadOtherScripts() { /** * Determine whether to skip the no-conflict mode in FluentCRM. * * This filter allows you to skip the no-conflict mode by returning true. * By default, it returns false, meaning the no-conflict mode is not skipped. * * @return bool Whether to skip the no-conflict mode. Default is false. */ $isSkip = apply_filters('fluent_crm/skip_no_conflict', false); if ($isSkip) { return; } /** * Define the list of approved slugs for FluentCRM assets. * * This filter allows modification of the list of slugs that are approved for FluentCRM assets. * * @param array $approvedSlugs An array of approved slugs for FluentCRM assets. */ $approvedSlugs = apply_filters('fluent_crm_asset_listed_slugs', [ '\/gutenberg\/' ]); $approvedSlugs[] = 'fluent-crm'; $approvedSlugs = array_unique($approvedSlugs); $approvedSlugs = implode('|', $approvedSlugs); $pluginUrl = str_replace(['http:', 'https:'], '', plugins_url()); add_filter('script_loader_src', function ($src, $handle) use ($approvedSlugs, $pluginUrl) { if (!$src) { return $src; } $willSkip = (strpos($src, $pluginUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src); if ($willSkip) { return false; } return $src; }, 1, 2); add_action('wp_print_scripts', function () { global $wp_scripts; if (!$wp_scripts) { return; } /** * Define the list of approved slugs for FluentCRM assets. * * This filter allows modification of the list of slugs that are approved for FluentCRM assets. * * @param array $approvedSlugs An array of approved slugs for FluentCRM assets. */ $approvedSlugs = apply_filters('fluent_crm_asset_listed_slugs', [ '\/gutenberg\/' ]); $approvedSlugs[] = 'fluent-crm'; $approvedSlugs = array_unique($approvedSlugs); $approvedSlugs = implode('|', $approvedSlugs); $pluginUrl = plugins_url(); $pluginUrl = str_replace(['http:', 'https:'], '', $pluginUrl); foreach ($wp_scripts->queue as $script) { if (empty($wp_scripts->registered[$script]) || empty($wp_scripts->registered[$script]->src)) { continue; } $src = $wp_scripts->registered[$script]->src; $isMatched = (strpos($src, $pluginUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src); if (!$isMatched) { continue; } wp_dequeue_script($wp_scripts->registered[$script]->handle); } }, 1); } private function getFunnelCatSvgIcon($key) { $icons = [ 'crm' => '', 'surecart' => '', 'fluentboards' => '', 'community' => '', 'fluentforms' => '', 'learndash' => '', 'lifterlms' => '', 'paidmembershipspro' => ' ', 'wordpresstriggers' => '', 'woocommerce' => '', 'tutorlms' => '', 'memberpress' => '', 'wishlistmember' => '' ]; return isset($icons[$key]) ? $icons[$key] : ''; } }