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] : '';
}
}