load_dependencies();
$this->init_components();
// Migrations touch posts and options: run them once WordPress is fully loaded,
// not while plugins are still being included (when this constructor runs).
add_action( 'init', [ $this, 'settings_version_check' ], 20 );
}
/** Prevent cloning. */
public function __clone() {}
/** Prevent unserialization. */
public function __wakeup() {}
// -------------------------------------------------------------------------
// Dependency Loading
// -------------------------------------------------------------------------
private function load_dependencies(): void {
// Database manager (must load before any class that queries DB)
require_once EPPRO_PATH . 'core/DB.php';
// Core managers
require_once EPPRO_PATH . 'core/Asset_Manager.php';
require_once EPPRO_PATH . 'core/widgets-manager.php';
// Shared Elementor helpers (widget lookup, safe template rendering)
require_once EPPRO_PATH . 'includes/class-elementor-helper.php';
// Admin & back-end services
require_once EPPRO_PATH . 'includes/class-admin-settings.php';
require_once EPPRO_PATH . 'includes/class-form-handler.php';
require_once EPPRO_PATH . 'includes/class-form-entries.php';
require_once EPPRO_PATH . 'includes/class-customizer.php';
// Setup wizard
require_once EPPRO_PATH . 'includes/class-setup-wizard.php';
// Advanced search AJAX handler
require_once EPPRO_PATH . 'includes/class-advanced-search-handler.php';
// Template duplicator
require_once EPPRO_PATH . 'includes/class-template-duplicator.php';
// Advanced features (v1.4.0+)
// Note: Display conditions are handled by EPPRO_Extension_Conditions (core/extensions/Conditions.php).
// The standalone class-display-conditions.php is no longer loaded to avoid dead code.
require_once EPPRO_PATH . 'includes/class-effects-manager.php';
// Popup builder system
require_once EPPRO_PATH . 'includes/popup/class-popup-cpt.php';
require_once EPPRO_PATH . 'includes/popup/class-popup-settings.php';
require_once EPPRO_PATH . 'includes/popup/class-popup-design.php';
require_once EPPRO_PATH . 'includes/popup/class-popup-admin.php';
require_once EPPRO_PATH . 'includes/popup/class-popup-renderer.php';
// Theme Builder (Header/Footer) system
require_once EPPRO_PATH . 'includes/theme-builder/class-theme-builder-cpt.php';
require_once EPPRO_PATH . 'includes/theme-builder/class-theme-builder-conditions.php';
require_once EPPRO_PATH . 'includes/theme-builder/class-theme-builder-admin.php';
require_once EPPRO_PATH . 'includes/theme-builder/class-theme-builder-renderer.php';
// Template shortcode
require_once EPPRO_PATH . 'includes/class-template-shortcode.php';
// Post Grid AJAX handler
require_once EPPRO_PATH . 'includes/class-post-grid-ajax.php';
// WooCommerce Products AJAX (Load More, Quick View)
require_once EPPRO_PATH . 'includes/class-woo-products-ajax.php';
// Read settings to decide which extensions to load
$eppro_s = get_option( 'eppro_settings', [] );
$disabled = (array) ( $eppro_s['disabled_extensions'] ?? [] );
// Dynamic Tags
if ( ! in_array( 'dynamic_tags', $disabled, true ) ) {
require_once EPPRO_PATH . 'core/dynamic-tags/Tag_Manager.php';
}
// Extensions (per-element controls)
if ( ! in_array( 'sticky', $disabled, true ) ) {
require_once EPPRO_PATH . 'core/extensions/Sticky.php';
}
if ( ! in_array( 'custom_css', $disabled, true ) ) {
require_once EPPRO_PATH . 'core/extensions/Custom_CSS.php';
}
if ( ! in_array( 'conditions', $disabled, true ) ) {
require_once EPPRO_PATH . 'core/extensions/Conditions.php';
}
}
// -------------------------------------------------------------------------
// Component Initialisation
// -------------------------------------------------------------------------
private function init_components(): void {
// ── Elementor hooks FIRST ─────────────────────────────────────────────
// These must be registered before anything else so that a failure in
// any later init() cannot prevent widgets from appearing in the editor.
add_action( 'elementor/elements/categories_registered', [ 'EPPRO_Widgets_Manager', 'register_category' ] );
add_action( 'elementor/widgets/register', [ 'EPPRO_Widgets_Manager', 'register_widgets' ] );
// ── DB ────────────────────────────────────────────────────────────────
try { EPPRO_DB::maybe_upgrade(); } catch ( \Throwable $e ) { eppro_log( 'EPP DB upgrade error: ' . $e->getMessage() ); }
// ── Core admin & asset services ───────────────────────────────────────
try {
EPPRO_Admin_Settings::init();
EPPRO_Form_Handler::init();
EPPRO_Form_Entries::init();
EPPRO_Asset_Manager::init();
EPPRO_Customizer::init();
} catch ( \Throwable $e ) { eppro_log( 'EPP core admin init error: ' . $e->getMessage() ); }
// ── Optional extensions ───────────────────────────────────────────────
try {
if ( class_exists( 'EPPRO_Tag_Manager' ) ) { EPPRO_Tag_Manager::init(); }
if ( class_exists( 'EPPRO_Extension_Sticky' ) ) { EPPRO_Extension_Sticky::init(); }
if ( class_exists( 'EPPRO_Extension_Conditions' ) ) { EPPRO_Extension_Conditions::init(); }
if ( class_exists( 'EPPRO_Extension_Custom_CSS' ) ) { EPPRO_Extension_Custom_CSS::init(); }
} catch ( \Throwable $e ) { eppro_log( 'EPP extensions init error: ' . $e->getMessage() ); }
// ── Setup wizard + utilities ──────────────────────────────────────────
try {
EPPRO_Setup_Wizard::init();
EPPRO_Advanced_Search_Handler::init();
EPPRO_Template_Duplicator::init();
// Display Conditions handled by EPPRO_Extension_Conditions (core/extensions).
EPPRO_Effects_Manager::init();
} catch ( \Throwable $e ) { eppro_log( 'EPP utilities init error: ' . $e->getMessage() ); }
// ── Popup system ──────────────────────────────────────────────────────
try {
EPPRO_Popup_CPT::init();
EPPRO_Popup_Settings::init();
EPPRO_Popup_Design::init();
EPPRO_Popup_Admin::init();
EPPRO_Popup_Renderer::init();
} catch ( \Throwable $e ) { eppro_log( 'EPP popup init error: ' . $e->getMessage() ); }
// ── Theme Builder system ──────────────────────────────────────────────
try {
EPPRO_Theme_Builder_CPT::init();
EPPRO_Theme_Builder_Admin::init();
EPPRO_Theme_Builder_Renderer::init();
EPPRO_Template_Shortcode::init();
} catch ( \Throwable $e ) { eppro_log( 'EPP theme builder init error: ' . $e->getMessage() ); }
// ── Post Grid / WooCommerce Products AJAX ─────────────────────────────
try { EPPRO_Post_Grid_AJAX::init(); } catch ( \Throwable $e ) { eppro_log( 'EPP post grid init error: ' . $e->getMessage() ); }
try { EPPRO_Woo_Products_AJAX::init(); } catch ( \Throwable $e ) { eppro_log( 'EPP woo products init error: ' . $e->getMessage() ); }
// ── WooCommerce: keep Mini Cart badge count in sync on AJAX add-to-cart ─
// Registered here (not in the widget file) so it fires on
// wc-ajax=get_refreshed_fragments requests where Elementor widgets are
// never registered. It is added unconditionally: this code runs while
// Elementor loads, before WooCommerce, so class_exists( 'WooCommerce' )
// would still be false here. The filter only fires when WooCommerce runs.
add_filter( 'woocommerce_add_to_cart_fragments', [ __CLASS__, 'mini_cart_fragment' ] );
}
/** Refreshed item count and subtotal for the Mini Cart widget. */
public static function mini_cart_fragment( $fragments ) {
if ( ! is_array( $fragments ) || ! function_exists( 'WC' ) || ! WC()->cart ) return $fragments;
$count = WC()->cart->get_cart_contents_count();
$fragments['.eppro-wmc-count'] = '' . esc_html( $count ) . '';
$fragments['.eppro-wmc-subtotal'] = '' . wp_kses_post( WC()->cart->get_cart_subtotal() ) . '';
return $fragments;
}
// -------------------------------------------------------------------------
// Settings Versioning — runs a migration when EPPRO_VERSION changes.
// -------------------------------------------------------------------------
public function settings_version_check(): void {
$stored = get_option( 'eppro_settings_version', '0.0.0' );
if ( version_compare( $stored, EPPRO_VERSION, '<' ) ) {
$this->migrate_old_prefix();
update_option( 'eppro_settings_version', EPPRO_VERSION );
// Widget style controls can change between versions (new CSS selectors).
// Let Elementor rebuild every page's CSS file once, lazily, so existing
// pages pick the new rules up without a manual "Regenerate CSS".
if ( class_exists( '\Elementor\Plugin' ) && isset( \Elementor\Plugin::$instance->files_manager ) ) {
\Elementor\Plugin::$instance->files_manager->clear_cache();
}
}
}
private function migrate_old_prefix(): void {
if ( get_option( 'eppro_prefix_migrated' ) ) return;
global $wpdb;
$option_map = [
'epp_settings' => 'eppro_settings',
'epp_settings_version' => 'eppro_settings_version',
'epp_setup_complete' => 'eppro_setup_complete',
'epp_db_version' => 'eppro_db_version',
'epp_customizer_settings' => 'eppro_customizer_settings',
'epp_popup_cpt_flushed' => 'eppro_popup_cpt_flushed',
'epp_popup_cpt_flushed_v2' => 'eppro_popup_cpt_flushed_v2',
'epp_hf_cpt_flushed' => 'eppro_hf_cpt_flushed',
'epp_feedback_notice_dismissed' => 'eppro_feedback_notice_dismissed',
'epp_show_feedback_notice' => 'eppro_show_feedback_notice',
'epp_deactivation_feedback_log' => 'eppro_deactivation_feedback_log',
];
foreach ( $option_map as $old => $new ) {
$val = get_option( $old );
if ( false !== $val && false === get_option( $new ) ) {
update_option( $new, $val );
delete_option( $old );
}
}
$old_popups = get_posts( [
'post_type' => 'epp_popup',
'post_status' => 'any',
'posts_per_page' => -1,
'fields' => 'ids',
'no_found_rows' => true,
] );
foreach ( $old_popups as $post_id ) {
wp_update_post( [ 'ID' => $post_id, 'post_type' => 'eppro_popup' ] );
}
$old_templates = get_posts( [
'post_type' => 'epp_hf_template',
'post_status' => 'any',
'posts_per_page' => -1,
'fields' => 'ids',
'no_found_rows' => true,
] );
foreach ( $old_templates as $post_id ) {
wp_update_post( [ 'ID' => $post_id, 'post_type' => 'eppro_hf_template' ] );
}
$old_table = $wpdb->prefix . 'epp_form_entries';
$new_table = $wpdb->prefix . 'eppro_form_entries';
// One-time migration of the plugin's own table (old "epp_" prefix → "eppro_").
// phpcs:disable WordPress.DB.DirectDatabaseQuery
$table_exists = wp_cache_get( 'eppro_old_table_check' );
if ( false === $table_exists ) {
$table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $old_table ) ) === $old_table;
wp_cache_set( 'eppro_old_table_check', $table_exists );
}
if ( $table_exists ) {
$wpdb->query( $wpdb->prepare( 'RENAME TABLE %i TO %i', $old_table, $new_table ) );
// phpcs:enable WordPress.DB.DirectDatabaseQuery
wp_cache_delete( 'eppro_old_table_check' );
}
// No rewrite flush needed: the plugin's post types register no rewrite rules.
update_option( 'eppro_prefix_migrated', '1' );
}
}