> */ public static function cards() { return array( self::elementor_atomic_card(), self::core_card(), self::plugin_card( 'acf', __( 'Advanced Custom Fields', 'reactwoo-atomic' ), __( 'Dynamic fields, repeaters and relationships', 'reactwoo-atomic' ), 'A', array( 'advanced-custom-fields/acf.php', 'advanced-custom-fields-pro/acf.php' ), function_exists( 'acf' ) || class_exists( 'ACF', false ), defined( 'ACF_VERSION' ) ? (string) ACF_VERSION : '' ), self::plugin_card( 'woocommerce', __( 'WooCommerce', 'reactwoo-atomic' ), __( 'Products, cart and conversion widgets', 'reactwoo-atomic' ), 'W', array( 'woocommerce/woocommerce.php' ), class_exists( 'WooCommerce', false ), defined( 'WC_VERSION' ) ? (string) WC_VERSION : '' ), self::plugin_card( 'pods', __( 'Pods', 'reactwoo-atomic' ), __( 'Custom fields and relationships', 'reactwoo-atomic' ), 'P', array( 'pods/init.php' ), function_exists( 'pods' ) || defined( 'PODS_VERSION' ), defined( 'PODS_VERSION' ) ? (string) PODS_VERSION : '' ), self::plugin_card( 'acpt', __( 'ACPT Pro', 'reactwoo-atomic' ), __( 'Custom post types and field data', 'reactwoo-atomic' ), 'AC', array( 'acpt/acpt.php', 'advanced-custom-post-type/acpt.php' ), defined( 'ACPT_PLUGIN_VERSION' ) || class_exists( 'ACPT_Plugin', false ), defined( 'ACPT_PLUGIN_VERSION' ) ? (string) ACPT_PLUGIN_VERSION : '' ), self::ai_card(), ); } /** * Count integrations needing attention (installed/setup but not connected). * * Optional plugins that are simply absent do not inflate the nav badge. * * @return int */ public static function attention_count() { $n = 0; foreach ( self::cards() as $card ) { if ( ! empty( $card['connected'] ) ) { continue; } $id = isset( $card['id'] ) ? (string) $card['id'] : ''; $status = isset( $card['status'] ) ? (string) $card['status'] : ''; // WP AI only matters for Pro Concierge. if ( 'wp-ai' === $id && ! Edition::is_pro() ) { continue; } // Elementor Atomic is required — always counts when not ready. if ( 'elementor-atomic' === $id && 'setup' !== $status && empty( $card['connected'] ) ) { ++$n; continue; } if ( 'setup' === $status ) { ++$n; continue; } if ( ! empty( $card['installed'] ) && in_array( $status, array( 'inactive', 'available' ), true ) ) { ++$n; } } return $n; } /** * Count actively connected integrations. * * @return int */ public static function connected_count() { $n = 0; foreach ( self::cards() as $card ) { if ( ! empty( $card['connected'] ) ) { ++$n; } } return $n; } /** * @return array */ private static function elementor_atomic_card() { $report = Atomic_Availability::report(); $elementor = ! empty( $report['elementor_loaded'] ); $ready = ! empty( $report['ready'] ); $version_ok = ! empty( $report['elementor_version_ok'] ); $base_ok = ! empty( $report['atomic_widget_base'] ) && ! empty( $report['atomic_element_base'] ); $files_ok = ! empty( $report['atomic_files_present'] ); $experiment = ! empty( $report['atomic_experiment'] ); $status = 'unavailable'; $label = __( 'Unavailable', 'reactwoo-atomic' ); if ( $ready ) { $status = 'connected'; $label = __( 'Editor V4 active', 'reactwoo-atomic' ); } elseif ( $elementor && $version_ok && $base_ok && ! $experiment ) { $status = 'setup'; $label = __( 'Enable Editor V4', 'reactwoo-atomic' ); } elseif ( $elementor && $version_ok && $files_ok && ! $base_ok ) { $status = 'setup'; $label = __( 'Enable Editor V4 / reload', 'reactwoo-atomic' ); } elseif ( $elementor && $version_ok && $base_ok ) { $status = 'setup'; $label = __( 'Enable Editor V4', 'reactwoo-atomic' ); } elseif ( $elementor ) { $status = 'setup'; $label = __( 'Update Elementor to 4.1+', 'reactwoo-atomic' ); } return array( 'id' => 'elementor-atomic', 'name' => __( 'Elementor Atomic (Editor V4)', 'reactwoo-atomic' ), 'description' => __( 'Required for native Atomic widgets (e_atomic_elements on Elementor 4.1+)', 'reactwoo-atomic' ), 'icon' => 'E', 'installed' => $elementor, 'active' => $elementor, 'connected' => $ready, 'version' => isset( $report['elementor_version'] ) ? (string) $report['elementor_version'] : '', 'status' => $status, 'status_label' => $label, 'action' => $ready ? 'manage' : 'connect', 'action_url' => admin_url( 'admin.php?page=elementor-settings#tab-experiments' ), 'extra' => array( 'min_version' => Atomic_Availability::MIN_ELEMENTOR_VERSION, 'report' => $report, ), 'compatibility' => 'required', ); } /** * @return array */ private static function core_card() { $provider = apply_filters( 'reactwoo/core/rule_provider/v1', null ); $is_null = is_object( $provider ) && is_a( $provider, '\\ReactWoo\\Atomic\\RuleContracts\\NullRuleProvider' ); $connected = is_object( $provider ) && ! $is_null; $core_active = self::is_plugin_active( 'reactwoo-geocore/reactwoo-geocore.php' ) || self::is_plugin_active( 'reactwoo-core/reactwoo-core.php' ) || defined( 'RWGC_VERSION' ); $status = 'unavailable'; $label = __( 'Unavailable', 'reactwoo-atomic' ); if ( $connected ) { $status = 'connected'; $label = __( 'Connected', 'reactwoo-atomic' ); } elseif ( $core_active ) { $status = 'inactive'; $label = __( 'Installed', 'reactwoo-atomic' ); } $extra = array(); if ( $connected && is_object( $provider ) ) { if ( method_exists( $provider, 'list_rules' ) ) { try { $rules = $provider->list_rules(); $extra['rules_count'] = is_array( $rules ) ? count( $rules ) : 0; } catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch $extra['rules_count'] = 0; } } $extra['placements'] = true; $extra['context_preview'] = true; $extra['provider_connected'] = true; } return array( 'id' => 'reactwoo-core', 'name' => __( 'ReactWoo Core', 'reactwoo-atomic' ), 'description' => __( 'Rules, audiences and experience variants', 'reactwoo-atomic' ), 'icon' => 'R', 'installed' => $core_active || $connected, 'active' => $core_active || $connected, 'connected' => $connected, 'version' => defined( 'RWGC_VERSION' ) ? (string) RWGC_VERSION : '', 'status' => $status, 'status_label'=> $label, 'action' => $connected ? 'manage' : 'learn', 'action_url' => $core_active ? admin_url( 'admin.php?page=rwgc-geocore' ) : 'https://reactwoo.com/geo-core/', 'extra' => $extra, 'compatibility'=> 'optional', ); } /** * @return array */ private static function ai_card() { $ready = false; if ( class_exists( '\\ReactWoo\\Atomic\\AiClient\\Availability', false ) ) { $ready = \ReactWoo\Atomic\AiClient\Availability::wp_ai_ready(); } else { global $wp_version; $ready = is_string( $wp_version ) && version_compare( $wp_version, '7.0', '>=' ) && ( class_exists( '\\WP_AI_Client', false ) || function_exists( 'wp_ai_client' ) ); } return array( 'id' => 'wp-ai', 'name' => __( 'WordPress AI Client', 'reactwoo-atomic' ), 'description' => __( 'Provider-neutral AI for Concierge widgets', 'reactwoo-atomic' ), 'icon' => 'AI', 'installed' => $ready, 'active' => $ready, 'connected' => $ready, 'version' => '', 'status' => $ready ? 'connected' : 'setup', 'status_label' => $ready ? __( 'Connected', 'reactwoo-atomic' ) : __( 'Setup required', 'reactwoo-atomic' ), 'action' => $ready ? 'manage' : 'connect', 'action_url' => admin_url( 'options-general.php' ), 'extra' => array(), 'compatibility'=> 'optional', ); } /** * @param string $id ID. * @param string $name Name. * @param string $description Description. * @param string $icon Icon letters. * @param list $basenames Plugin basenames. * @param bool $runtime Runtime detection. * @param string $version Version string. * @return array */ private static function plugin_card( $id, $name, $description, $icon, array $basenames, $runtime, $version ) { $installed = $runtime; foreach ( $basenames as $basename ) { if ( self::is_plugin_installed( $basename ) ) { $installed = true; break; } } $active = $runtime; foreach ( $basenames as $basename ) { if ( self::is_plugin_active( $basename ) ) { $active = true; break; } } $connected = $active && $runtime; $status = 'unavailable'; $label = __( 'Unavailable', 'reactwoo-atomic' ); if ( $connected ) { $status = 'connected'; $label = __( 'Connected', 'reactwoo-atomic' ); } elseif ( $installed ) { $status = 'available'; $label = __( 'Available', 'reactwoo-atomic' ); } return array( 'id' => $id, 'name' => $name, 'description' => $description, 'icon' => $icon, 'installed' => $installed, 'active' => $active, 'connected' => $connected, 'version' => $version, 'status' => $status, 'status_label' => $label, 'action' => $connected ? 'manage' : 'connect', 'action_url' => admin_url( 'plugins.php' ), 'extra' => array(), 'compatibility'=> $connected ? 'compatible' : 'optional', ); } /** * @param string $basename Plugin basename. * @return bool */ private static function is_plugin_active( $basename ) { if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return function_exists( 'is_plugin_active' ) && is_plugin_active( $basename ); } /** * @param string $basename Plugin basename. * @return bool */ private static function is_plugin_installed( $basename ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( ! function_exists( 'get_plugins' ) ) { return false; } $plugins = get_plugins(); return isset( $plugins[ $basename ] ); } }