From e1b889db251fc3e1ddb9e1376104bccfa08848ba Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 19 Aug 2026 17:53:14 +0530 Subject: [PATCH 1/3] fix: add class existence checks to prevent errors --- globals/migrations.php | 3 + .../Core/Builder/Abstract_Builder.php | 2 +- inc/admin/dashboard/main.php | 70 +++++++++++++------ inc/core/admin.php | 5 ++ inc/core/core_loader.php | 39 +++++++++-- inc/core/dynamic_css.php | 21 ++++-- inc/core/factory.php | 2 +- inc/core/settings/mods_migrator.php | 4 ++ inc/core/styles/generator.php | 4 ++ inc/customizer/loader.php | 3 + tests/test-neve-autoloader.php | 13 ++++ 11 files changed, 135 insertions(+), 31 deletions(-) diff --git a/globals/migrations.php b/globals/migrations.php index c74c608a68..e7f4b8a8bb 100644 --- a/globals/migrations.php +++ b/globals/migrations.php @@ -127,6 +127,9 @@ function neve_migrate_blog_columns() { * @return void */ function neve_run_migration_flags() { + if ( ! class_exists( Migration_Flags::class ) ) { + return; + } $migrator = new Migration_Flags( NEVE_VERSION ); $migrator->run(); } diff --git a/header-footer-grid/Core/Builder/Abstract_Builder.php b/header-footer-grid/Core/Builder/Abstract_Builder.php index 7c8c0e4936..ce82a65b12 100644 --- a/header-footer-grid/Core/Builder/Abstract_Builder.php +++ b/header-footer-grid/Core/Builder/Abstract_Builder.php @@ -826,7 +826,7 @@ public function get_current_slot_index() { public function render() { $layout = $this->get_layout_data(); self::$current_builder = $this->get_id(); - if ( is_customize_preview() ) { + if ( is_customize_preview() && class_exists( Css_Generator::class ) ) { $style = $this->add_style( [] ); $generator = new Css_Generator(); $generator->set( $style ); diff --git a/inc/admin/dashboard/main.php b/inc/admin/dashboard/main.php index d7dd79bdec..a73e2cc06a 100755 --- a/inc/admin/dashboard/main.php +++ b/inc/admin/dashboard/main.php @@ -22,13 +22,13 @@ class Main { /** * Changelog Handler. * - * @var Changelog_Handler + * @var Changelog_Handler|null */ private $cl_handler; /** * Plugin Helper instance. * - * @var Plugin_Helper + * @var Plugin_Helper|null */ private $plugin_helper; /** @@ -42,8 +42,50 @@ class Main { * Main constructor. */ public function __construct() { - $this->plugin_helper = new Plugin_Helper(); - $this->cl_handler = new Changelog_Handler(); + if ( class_exists( Plugin_Helper::class ) ) { + $this->plugin_helper = new Plugin_Helper(); + } + + if ( class_exists( Changelog_Handler::class ) ) { + $this->cl_handler = new Changelog_Handler(); + } + } + + /** + * Read a changelog through the handler, if there is one. + * + * @param string $path Absolute path to the changelog file. + * + * @return mixed[] + */ + private function get_changelog( $path ) { + if ( null === $this->cl_handler ) { + return array(); + } + + return $this->cl_handler->get_changelog( $path ); + } + + /** + * Plugin action data for a recommended plugin card. + * + * @param string $slug Plugin slug. + * + * @return array + */ + private function get_plugin_actions( $slug ) { + if ( null === $this->plugin_helper ) { + return array(); + } + + return [ + 'cta' => $this->plugin_helper->get_plugin_state( $slug ), + 'path' => $this->plugin_helper->get_plugin_path( $slug ), + 'activate' => $this->plugin_helper->get_plugin_action_link( $slug ), + 'deactivate' => $this->plugin_helper->get_plugin_action_link( $slug, 'deactivate' ), + 'network' => $this->plugin_helper->get_is_network_wide( $slug ), + 'version' => $this->plugin_helper->get_plugin_version( $slug, '0.0.0' ), + ]; } /** @@ -350,7 +392,7 @@ private function get_localization() { 'Themeisle' . esc_html__( '(opens in a new tab)', 'neve' ) . '' ), ], - 'changelog' => $this->cl_handler->get_changelog( get_template_directory() . '/CHANGELOG.md' ), + 'changelog' => $this->get_changelog( get_template_directory() . '/CHANGELOG.md' ), 'onboarding' => [], 'hasFileSystem' => WP_Filesystem(), 'hidePluginsTab' => apply_filters( 'neve_hide_useful_plugins', ! array_key_exists( 'useful_plugins', $old_about_config ) ), @@ -369,7 +411,7 @@ private function get_localization() { 'orbitFox' => array( 'isInstalled' => file_exists( WP_PLUGIN_DIR . '/themeisle-companion/themeisle-companion.php' ), 'isActive' => class_exists( 'Orbit_Fox' ), - 'activationUrl' => $this->plugin_helper->get_plugin_action_link( 'themeisle-companion' ), + 'activationUrl' => null === $this->plugin_helper ? '' : $this->plugin_helper->get_plugin_action_link( 'themeisle-companion' ), 'data' => class_exists( 'Orbit_Fox' ) ? get_option( 'obfx_data' ) : array(), ), ]; @@ -378,7 +420,7 @@ private function get_localization() { $installed_plugins = get_plugins(); $is_otter_installed = array_key_exists( 'otter-pro/otter-pro.php', $installed_plugins ); $is_sparks_installed = array_key_exists( 'sparks-for-woocommerce/sparks-for-woocommerce.php', $installed_plugins ); - $data['changelogPro'] = $this->cl_handler->get_changelog( NEVE_PRO_PATH . '/CHANGELOG.md' ); + $data['changelogPro'] = $this->get_changelog( NEVE_PRO_PATH . '/CHANGELOG.md' ); $data['isOtterProInstalled'] = $is_otter_installed; $data['otterProInstall'] = $is_otter_installed ? esc_url( wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=otter-pro%2Fotter-pro.php&plugin_status=all&paged=1&s' ), 'activate-plugin_otter-pro/otter-pro.php' ) ) : esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=install_otter_pro' ), 'install_otter_pro' ) ); $data['sparksInstallActivateEndpoint'] = $is_sparks_installed ? esc_url( wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=sparks-for-woocommerce%2Fsparks-for-woocommerce.php&plugin_status=all&paged=1&s' ), 'activate-plugin_sparks-for-woocommerce/sparks-for-woocommerce.php' ) ) : esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=install_sparks' ), 'install_sparks' ) ); @@ -907,19 +949,7 @@ private function get_recommended_plugins() { continue; } - $action = $this->plugin_helper->get_plugin_state( $slug ); - - $plugins[ $slug ] = array_merge( - [ - 'cta' => $action, - 'path' => $this->plugin_helper->get_plugin_path( $slug ), - 'activate' => $this->plugin_helper->get_plugin_action_link( $slug ), - 'deactivate' => $this->plugin_helper->get_plugin_action_link( $slug, 'deactivate' ), - 'network' => $this->plugin_helper->get_is_network_wide( $slug ), - 'version' => $this->plugin_helper->get_plugin_version( $slug, '0.0.0' ), - ], - $args - ); + $plugins[ $slug ] = array_merge( $this->get_plugin_actions( $slug ), $args ); } return $plugins; diff --git a/inc/core/admin.php b/inc/core/admin.php index b38b6a7310..d234886dec 100644 --- a/inc/core/admin.php +++ b/inc/core/admin.php @@ -264,6 +264,11 @@ public function migrate_theme_mods_for_new_skin( $theme_mods ) { if ( ! neve_is_new_skin() ) { return $theme_mods; } + + if ( ! class_exists( Mods_Migrator::class ) ) { + return $theme_mods; + } + $migrator = new Mods_Migrator( $theme_mods ); return $migrator->get_migrated_mods(); diff --git a/inc/core/core_loader.php b/inc/core/core_loader.php index 462bcceab9..01b1385f5c 100644 --- a/inc/core/core_loader.php +++ b/inc/core/core_loader.php @@ -149,6 +149,10 @@ private function define_modules() { private function load_modules() { do_action( 'neve_before_modules_load' ); + if ( ! class_exists( Factory::class ) ) { + return; + } + $factory = new Factory( $this->features ); $factory->load_modules(); } @@ -164,10 +168,9 @@ private function define_hooks() { if ( is_customize_preview() ) { Mods::$no_cache = true; } - $admin = new Admin(); - add_action( 'init', array( $admin, 'load_site_import' ), 20 ); - add_action( 'admin_enqueue_scripts', array( $admin, 'register_react_components' ), 0 ); - add_action( 'ti-about-after-sidebar-content', array( $admin, 'render_logger_toggle' ) ); + if ( class_exists( Admin::class ) ) { + $this->load_admin_hooks(); + } $key_lite = str_replace( '-', '_', basename( get_template_directory() ) ); add_filter( @@ -176,6 +179,34 @@ function () { return [ 'mods' => array_filter( get_theme_mods() ) ]; } ); + + if ( class_exists( Front_End::class ) ) { + $this->load_front_end_hooks(); + } + } + + /** + * Load admin hooks. + * + * @access private + * + * @return void + */ + private function load_admin_hooks() { + $admin = new Admin(); + add_action( 'init', array( $admin, 'load_site_import' ), 20 ); + add_action( 'admin_enqueue_scripts', array( $admin, 'register_react_components' ), 0 ); + add_action( 'ti-about-after-sidebar-content', array( $admin, 'render_logger_toggle' ) ); + } + + /** + * Load front end hooks. + * + * @access private + * + * @return void + */ + private function load_front_end_hooks() { $front_end = new Front_End(); add_action( 'wp_enqueue_scripts', array( $front_end, 'enqueue_scripts' ) ); add_action( 'after_setup_theme', array( $front_end, 'setup_theme' ) ); diff --git a/inc/core/dynamic_css.php b/inc/core/dynamic_css.php index 90b8838789..53546dbe78 100644 --- a/inc/core/dynamic_css.php +++ b/inc/core/dynamic_css.php @@ -41,6 +41,10 @@ public function legacy_style() { $desktop_css = ''; $tablet_css = ''; foreach ( $classes as $class ) { + if ( ! class_exists( $class ) ) { + continue; + } + $object = new $class(); $object->init(); $mobile_css .= $object->get_style( 'mobile' ); @@ -68,14 +72,21 @@ public function enqueue() { $this->legacy_style(); } - $this->generator = $is_for_gutenberg ? new Gutenberg() : new Frontend(); - $_subscribers = $this->generator->get(); + $generator_class = $is_for_gutenberg ? Gutenberg::class : Frontend::class; + $generated_css = ''; + + if ( class_exists( $generator_class ) ) { + $this->generator = new $generator_class(); + $_subscribers = $this->generator->get(); - $_subscribers = array_merge( $_subscribers, apply_filters( 'neve_style_subscribers', [] ) ); + $_subscribers = array_merge( $_subscribers, apply_filters( 'neve_style_subscribers', [] ) ); - $this->generator->set( $_subscribers ); + $this->generator->set( $_subscribers ); + + $generated_css = $this->generator->generate(); + } - $style = apply_filters( 'neve_dynamic_style_output', $this->generator->generate(), $is_for_gutenberg ? 'gutenberg' : 'frontend' ); + $style = apply_filters( 'neve_dynamic_style_output', $generated_css, $is_for_gutenberg ? 'gutenberg' : 'frontend' ); $style .= self::get_root_css(); diff --git a/inc/core/factory.php b/inc/core/factory.php index 3d3ae31f24..36562e067f 100644 --- a/inc/core/factory.php +++ b/inc/core/factory.php @@ -51,7 +51,7 @@ public function __construct( $modules, $namespace = '\\Neve\\' ) { public function load_modules() { foreach ( $this->modules as $module_name ) { $module = $this->build( $module_name ); - if ( $module !== null ) { + if ( $module !== null && method_exists( $module, 'init' ) ) { $module->init(); } } diff --git a/inc/core/settings/mods_migrator.php b/inc/core/settings/mods_migrator.php index 41478cbfde..bd3d4ee62b 100644 --- a/inc/core/settings/mods_migrator.php +++ b/inc/core/settings/mods_migrator.php @@ -124,6 +124,10 @@ private function migrate_mods() { * @return void */ private function attempt_builders_migration() { + if ( ! class_exists( Builder_Migrator::class ) ) { + return; + } + $hfg_migrator = new Builder_Migrator(); foreach ( $this->builder_map as $builder ) { diff --git a/inc/core/styles/generator.php b/inc/core/styles/generator.php index 85d5c8e953..e6e19e729e 100644 --- a/inc/core/styles/generator.php +++ b/inc/core/styles/generator.php @@ -41,6 +41,10 @@ class Generator { * @return string|void Css output. */ public function generate( $echo = false ) { + if ( ! class_exists( Dynamic_Selector::class ) ) { + return ''; + } + $desktop_css = ''; $tablet_css = ''; $all_css = ''; diff --git a/inc/customizer/loader.php b/inc/customizer/loader.php index bf91a3b9f5..94bbe8b790 100644 --- a/inc/customizer/loader.php +++ b/inc/customizer/loader.php @@ -281,6 +281,9 @@ public function set_featured_image() { * @return void */ private function load_modules() { + if ( ! class_exists( Factory::class ) ) { + return; + } $factory = new Factory( $this->customizer_modules ); $factory->load_modules(); } diff --git a/tests/test-neve-autoloader.php b/tests/test-neve-autoloader.php index 283346923f..ecb1a3e55e 100644 --- a/tests/test-neve-autoloader.php +++ b/tests/test-neve-autoloader.php @@ -193,4 +193,17 @@ public function testFailedLookupLeavesClassLoadableByNextAutoloader() { spl_autoload_unregister( array( $autoloader, 'load_class' ) ); } } + + /** + * A miss inside a registered namespace is recorded so the admin can be told + * which file is gone, instead of the feature disappearing silently. + */ + public function testMissingMappedClassIsRecorded() { + $autoloader = new \Neve\Autoloader(); + $autoloader->add_namespace( 'Neve_Fixture', $this->fixture_dir ); + + $is_loaded = $autoloader->load_class( 'Neve_Fixture\\Widgets\\Absent_Widget' ); + + $this->assertFalse( $is_loaded ); + } } From 36bf2f0d4d3a26fb1214a792be9cf5d8417b92c3 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 19 Aug 2026 18:31:20 +0530 Subject: [PATCH 2/3] fix: improve class loading error handling --- inc/admin/dashboard/main.php | 9 ++++++++- inc/core/factory.php | 15 ++++++++++++++- inc/core/styles/generator.php | 6 +++++- tests/test-neve-autoloader.php | 30 +++++++++++++++++++++++++----- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/inc/admin/dashboard/main.php b/inc/admin/dashboard/main.php index a73e2cc06a..210f1cff08 100755 --- a/inc/admin/dashboard/main.php +++ b/inc/admin/dashboard/main.php @@ -75,7 +75,14 @@ private function get_changelog( $path ) { */ private function get_plugin_actions( $slug ) { if ( null === $this->plugin_helper ) { - return array(); + return [ + 'cta' => 'install', + 'path' => '', + 'activate' => '', + 'deactivate' => '', + 'network' => false, + 'version' => '0.0.0', + ]; } return [ diff --git a/inc/core/factory.php b/inc/core/factory.php index 36562e067f..7bebf8dd3f 100644 --- a/inc/core/factory.php +++ b/inc/core/factory.php @@ -51,8 +51,21 @@ public function __construct( $modules, $namespace = '\\Neve\\' ) { public function load_modules() { foreach ( $this->modules as $module_name ) { $module = $this->build( $module_name ); - if ( $module !== null && method_exists( $module, 'init' ) ) { + if ( $module === null ) { + continue; + } + if ( method_exists( $module, 'init' ) ) { $module->init(); + continue; + } + + $message = sprintf( + 'Module "%s" was built but does not implement an init() method.', + $this->namespace . $module_name + ); + if ( function_exists( '_doing_it_wrong' ) ) { + _doing_it_wrong( __METHOD__, esc_html( $message ), '1.0.0' ); + continue; } } } diff --git a/inc/core/styles/generator.php b/inc/core/styles/generator.php index e6e19e729e..ec83ec8ec1 100644 --- a/inc/core/styles/generator.php +++ b/inc/core/styles/generator.php @@ -42,7 +42,11 @@ class Generator { */ public function generate( $echo = false ) { if ( ! class_exists( Dynamic_Selector::class ) ) { - return ''; + if ( ! $echo ) { + return ''; + } + echo ''; + return; } $desktop_css = ''; diff --git a/tests/test-neve-autoloader.php b/tests/test-neve-autoloader.php index ecb1a3e55e..4c33d9accf 100644 --- a/tests/test-neve-autoloader.php +++ b/tests/test-neve-autoloader.php @@ -195,15 +195,35 @@ public function testFailedLookupLeavesClassLoadableByNextAutoloader() { } /** - * A miss inside a registered namespace is recorded so the admin can be told - * which file is gone, instead of the feature disappearing silently. + * A miss inside a registered namespace resolves to false rather than + * requiring a file that is not there. The caller is what has to guard; the + * autoloader only reports. */ - public function testMissingMappedClassIsRecorded() { + public function testMissingMappedClassResolvesToFalse() { $autoloader = new \Neve\Autoloader(); $autoloader->add_namespace( 'Neve_Fixture', $this->fixture_dir ); - $is_loaded = $autoloader->load_class( 'Neve_Fixture\\Widgets\\Absent_Widget' ); + $this->assertFalse( $autoloader->load_class( 'Neve_Fixture\\Widgets\\Absent_Widget' ) ); + $this->assertFalse( class_exists( 'Neve_Fixture\\Widgets\\Absent_Widget', false ) ); + } + + /** + * A miss must not be remembered. The file can appear later in the same + * request, and a cached negative would keep the class unreachable. + */ + public function testMissIsNotCachedAgainstALaterFile() { + $autoloader = new \Neve\Autoloader(); + $autoloader->add_namespace( 'Neve_Fixture', $this->fixture_dir ); + + $this->assertFalse( $autoloader->load_class( 'Neve_Fixture\\Widgets\\Deferred_Widget' ) ); + + mkdir( $this->fixture_dir . 'widgets/', 0777, true ); + file_put_contents( + $this->fixture_dir . 'widgets/deferred_widget.php', + 'assertFalse( $is_loaded ); + $this->assertNotFalse( $autoloader->load_class( 'Neve_Fixture\\Widgets\\Deferred_Widget' ) ); + $this->assertTrue( class_exists( 'Neve_Fixture\\Widgets\\Deferred_Widget', false ) ); } } From 87c6a7b96854c14ab2cc1d1c20a878eb1b1c5e8a Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 19 Aug 2026 18:54:14 +0530 Subject: [PATCH 3/3] fix: use current theme version for error reporting --- inc/core/factory.php | 2 +- inc/core/styles/generator.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/inc/core/factory.php b/inc/core/factory.php index 7bebf8dd3f..c78a2cb1c7 100644 --- a/inc/core/factory.php +++ b/inc/core/factory.php @@ -64,7 +64,7 @@ public function load_modules() { $this->namespace . $module_name ); if ( function_exists( '_doing_it_wrong' ) ) { - _doing_it_wrong( __METHOD__, esc_html( $message ), '1.0.0' ); + _doing_it_wrong( __METHOD__, esc_html( $message ), '4.2.12' ); continue; } } diff --git a/inc/core/styles/generator.php b/inc/core/styles/generator.php index ec83ec8ec1..4438eb5f81 100644 --- a/inc/core/styles/generator.php +++ b/inc/core/styles/generator.php @@ -45,7 +45,6 @@ public function generate( $echo = false ) { if ( ! $echo ) { return ''; } - echo ''; return; }