['path' => '/abs/path/to/file.js', 'type' => 'script'], ... ] * @var array */ private array $disguisable_assets = []; /** * Get the singleton instance. */ public static function get(): self { if ( self::$instance === null ) { self::$instance = new self(); } return self::$instance; } /** * Register all necessary hooks. */ public function hooks(): void { $this->register_all_assets(); add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_frontend_assets' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); add_action( 'wp_print_styles', [ $this, 'requeue_style_carrier' ] ); add_action( 'wp_print_footer_scripts', [ $this, 'requeue_script_carrier' ], 5 ); add_filter( 'adpresso_dynamic_content_support', [ self::class, 'support_jetpack_infinite_scroll' ] ); add_filter( 'adpresso_dynamic_content_support', [ self::class, 'support_ajax_load_more' ] ); } /** * Ajax Load More can sit in any template, so its pages can't be told apart before they render. * * @param bool $supported * * @return bool */ public static function support_ajax_load_more( $supported ): bool { return $supported || class_exists( 'AjaxLoadMore', false ); } /** * Jetpack Infinite Scroll loads more posts into home, archive and search pages (handled in dynamic-content.js). * * @param bool $supported * * @return bool */ public static function support_jetpack_infinite_scroll( $supported ): bool { if ( $supported ) { return true; } return class_exists( 'The_Neverending_Home_Page', false ) && current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() || is_search() ); } /** * Third parties that dequeue every style without a plugin path in its src (e.g. BuddyBoss ReadyLaunch) also drop our src-less carrier and all inline CSS attached to it. */ public function requeue_style_carrier(): void { if ( ! wp_style_is( 'adpresso-main-frontend', 'enqueued' ) && ! empty( wp_styles()->get_data( 'adpresso-main-frontend', 'after' ) ) ) { wp_enqueue_style( 'adpresso-main-frontend' ); } } /** * Same for the src-less script carrying window.adpressoData - it only survives such a dequeue when another script depends on it. */ public function requeue_script_carrier(): void { if ( ! wp_script_is( 'adpresso-main-frontend', 'enqueued' ) && ! empty( wp_scripts()->get_data( 'adpresso-main-frontend', 'data' ) ) ) { wp_enqueue_script( 'adpresso-main-frontend' ); } } /** * Registers all scripts and styles used in the plugin. * This method acts as the central "catalogue" for all assets. * Add-ons can hook into 'adpresso_register_assets' to add their own. */ public function register_all_assets(): void { $this->register_core_assets(); /** * Action hook for add-ons to register their own scripts and styles. * * @param Assets $this The instance of the Assets class. */ do_action( 'adpresso_register_assets', $this ); } /** * Handles the conditional enqueuing of frontend scripts and styles. */ public function enqueue_frontend_assets(): void { // Ensure the pipeline has run to determine if JS placements are needed. Runtime::maybe_execute_processor(); // main-frontend (src-less: the window.adpressoData localize anchor + inline-style carrier) is always enqueued and costs no request. $this->enqueue_script( 'main-frontend' ); $this->enqueue_style( 'main-frontend' ); // Conditionally enqueue the client rendering engine. if ( Runtime::render_chain_required() || Runtime::dynamic_content_support() ) { self::enqueue_render_chain(); } if ( current_user_can( adpresso_get_capability() ) ) { $this->enqueue_script( 'adminbar' ); } } /** * Enqueues the frontend rendering engine (condition evaluator, scheduling * check, placement controller, rendering pipeline, impression capping). * */ public static function enqueue_render_chain(): void { foreach ( [ 'rendering-pipeline', 'condition-evaluator', 'scheduling-check', 'placement-controller', 'impression-capping', 'dynamic-content' ] as $handle ) { wp_enqueue_script( 'adpresso-' . $handle ); } } /** * Handles the conditional enqueuing of admin scripts and styles. * * @param string $hook_suffix The hook suffix of the current admin page. */ public function enqueue_admin_assets( string $hook_suffix ): void { // Everything below is only needed on AdPresso's own admin screens. if ( Util::is_adpresso_admin_screen() ) { $this->enqueue_script( 'core-events' ); $this->enqueue_style( 'alignment' ); wp_enqueue_style( 'wp-components' ); $this->enqueue_style( 'backend' ); $this->enqueue_global_admin_scripts(); $this->localize_admin_scripts(); } /** @var Admin_Menu $admin_menu */ $admin_menu = Plugin::get()->globals[ Admin_Menu::class ] ?? false; if ( ! $admin_menu ) { return; } $screens = $admin_menu->get_screens(); $wp_screen = get_current_screen(); if ( ! $wp_screen ) { return; } // Enqueue assets for the list table view of the CPTs. foreach ( $screens as $screen ) { if ( method_exists( $screen, 'is_relevant_for' ) && $screen->is_relevant_for( $wp_screen ) ) { if ( method_exists( $screen, 'enqueue_assets' ) ) { // Pass this Assets instance to the screen for enqueuing. $screen->enqueue_assets( $wp_screen ); } break; } } } /** * Returns a filterable list of all frontend assets that can be disguised. * This is the central entry point for the Asset Disguise module. * * @return array */ public function get_disguisable_assets(): array { /** * Filters the list of frontend assets that are candidates for disguising. */ return apply_filters( 'adpresso_disguisable_assets', $this->disguisable_assets ); } /** * Registers all core scripts and styles of the AdPresso base plugin. */ private function register_core_assets(): void { // Frontend Scripts --- // main-frontend is the wp_localize_script anchor for window.adpressoData wp_register_script( 'adpresso-main-frontend', false, [], ADPRESSO_VERSION, true ); if ( FrontendAssetBundles::is_active() ) { // core + renderer bundles, with the individual handles kept as src=false aliases. FrontendAssetBundles::register( $this ); } else { $this->register_script( 'core-events', 'frontend/assets/js/core-events.js', [ 'adpresso-main-frontend' ], true ); $this->register_script( 'core-utils', 'frontend/assets/js/utils.js', [ 'adpresso-core-events' ], true ); $this->register_script( 'condition-evaluator', 'frontend/assets/js/condition-evaluator.js', [ 'adpresso-main-frontend', 'adpresso-core-events' ], true ); $this->register_script( 'global-gates', 'frontend/assets/js/global-gates.js', [ 'adpresso-core-events' ], true ); $this->register_script( 'scheduling-check', 'frontend/assets/js/scheduling-check.js', [ 'adpresso-core-events' ], true ); $this->register_script( 'placement-controller', 'frontend/assets/js/placement-controller.js', [ 'adpresso-core-events', 'adpresso-condition-evaluator', 'adpresso-global-gates', 'adpresso-scheduling-check' ], true ); $this->register_script( 'impression-capping', 'frontend/assets/js/impression-capping.js', [ 'adpresso-core-events' ], true ); $this->register_script( 'rendering-pipeline', 'frontend/assets/js/rendering-pipeline.js', [ 'adpresso-core-events', 'adpresso-core-utils', 'adpresso-condition-evaluator', 'adpresso-placement-controller' ], true ); $this->register_script( 'dynamic-content', 'frontend/assets/js/dynamic-content.js', [ 'adpresso-rendering-pipeline' ], true ); } $this->register_script( 'adminbar', 'frontend/assets/js/adminbar.js', [ 'adpresso-core-events' ], true ); // Debug-only style: the admin-bar debug panel + placement-highlight CSS. Enqueued // solely by RuntimeDebugger::enqueue_assets() when the debugger is active - never on // the anonymous front-end critical path. Not masked (debug UI for admins only). wp_register_style( 'adpresso-adminbar', plugin_dir_url( ADPRESSO_FILE ) . 'frontend/assets/css/build/adminbar-debug.css', [], ADPRESSO_VERSION ); // Its bait scripts are not registered: AdblockDetection loads them by URL, so Asset Masking can't rename them. $this->register_script( 'adblocker-check', 'frontend/assets/js/adblocker-check.js', [], true, [ 'in_footer' => false ] ); // Frontend Styles --- // Src-less carrier handle: emits no and no HTTP request. It exists only so wp_add_inline_style( 'adpresso-main-frontend', ... ) has an enqueued target for the per-placement-type CSS. wp_register_style( 'adpresso-main-frontend', false, [], ADPRESSO_VERSION ); // Admin Scripts (React Apps & others) --- $this->register_asset_from_manifest( 'admin/assets/app/build/' ); $this->register_asset_from_manifest( 'admin/assets/header/build/', false, [ 'wp-api-fetch' ] ); $this->register_asset_from_manifest( 'admin/assets/block/build/' ); $this->register_asset_from_manifest( 'admin/assets/condition-builder/build/' ); $this->register_asset_from_manifest( 'admin/assets/modal/build/', false, [ 'wp-api-fetch', 'code-editor' ] ); $this->register_script( 'admin-main', 'admin/assets/src/backend.js' ); // Admin Styles --- $this->register_style( 'backend', 'admin/assets/styles/build/admin-styles.css' ); } /** * Enqueues scripts that should be available on all admin pages. */ private function enqueue_global_admin_scripts(): void { wp_enqueue_script( 'adpresso-admin-main' ); } /** * Localizes data for admin scripts. * * @return void */ private function localize_admin_scripts(): void { $data = [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'dateFormat' => get_option( 'date_format' ), 'timeFormat' => get_option( 'time_format' ), 'isPro' => Util::is_pro_active(), 'pricingUrl' => adpresso_fs()->get_upgrade_url(), ]; $data = apply_filters( 'adpresso_localize_admin_scripts', $data ); wp_localize_script( 'adpresso-admin-main', 'adpressoAdminAjax', $data ); } /** * Helper to register a script. * * @param string $handle Handle for the script (without prefix). * @param string $relative_path Path to the script relative to the plugin root. * @param array $deps Dependencies. * @param bool $is_frontend Is this an asset for the public frontend? */ public function register_script( string $handle, string $relative_path, array $deps = [], bool $is_frontend = false, $args = true ): void { $prefixed_handle = 'adpresso-' . $handle; /** * Filters the dependencies for a registered script. * * @param array $deps The original array with dependencies. * @param string $handle The not-already prefixed handle of the script, e.g. 'main-frontend'. */ $filtered_deps = apply_filters( 'adpresso_script_dependencies', $deps, $handle ); // Serve the minified build when there is one. Resolved before both the registered // URL and the disguise path so the Asset Masking module masks the same file. if ( $is_frontend ) { $relative_path = FrontendAssetOptimizer::resolve( $relative_path ); } wp_register_script( $prefixed_handle, plugin_dir_url( ADPRESSO_FILE ) . $relative_path, $filtered_deps, ADPRESSO_VERSION, is_array( $args ) ? $args : true ); if ( $is_frontend ) { $this->disguisable_assets[ $prefixed_handle ] = [ 'path' => plugin_dir_path( ADPRESSO_FILE ) . $relative_path, 'type' => 'script', ]; } } /** * Helper to register a style. */ public function register_style( string $handle, string $relative_path, array $deps = [], bool $is_frontend = false ): void { $prefixed_handle = 'adpresso-' . $handle; wp_register_style( $prefixed_handle, plugin_dir_url( ADPRESSO_FILE ) . $relative_path, $deps, ADPRESSO_VERSION ); if ( $is_frontend ) { $this->disguisable_assets[ $prefixed_handle ] = [ 'path' => plugin_dir_path( ADPRESSO_FILE ) . $relative_path, 'type' => 'style', ]; } } /** * Helper to register a script/style from a WordPress asset manifest file (index.asset.php). * * @param string $build_path_relative The path to the build directory, relative to the plugin root. * @param bool $is_frontend Is this an asset for the public frontend? */ public function register_asset_from_manifest( string $build_path_relative, bool $is_frontend = false, $additional_script_dependencies = [], $additional_style_dependencies = [], $custom_handle = '' ): void { $manifest_path = plugin_dir_path( ADPRESSO_FILE ) . $build_path_relative . 'index.asset.php'; if ( ! file_exists( $manifest_path ) ) { return; } $asset = require $manifest_path; if ( $custom_handle !== '' ) { $handle = 'adpresso-' . $custom_handle; } else { $handle = 'adpresso-' . basename( dirname( rtrim( $build_path_relative, '/' ) ) ); } wp_register_script( $handle, plugin_dir_url( ADPRESSO_FILE ) . $build_path_relative . 'index.js', array_merge( $asset['dependencies'], $additional_script_dependencies ), $asset['version'], true ); if ( $is_frontend ) { $this->disguisable_assets[ $handle ] = [ 'path' => plugin_dir_path( ADPRESSO_FILE ) . $build_path_relative . 'index.js', 'type' => 'script', ]; } $style_path = plugin_dir_path( ADPRESSO_FILE ) . $build_path_relative . 'index.css'; if ( file_exists( $style_path ) ) { wp_register_style( $handle, plugin_dir_url( ADPRESSO_FILE ) . $build_path_relative . 'index.css', $additional_style_dependencies, $asset['version'] ); if ( $is_frontend ) { $this->disguisable_assets[ $handle . '-style' ] = [ 'path' => $style_path, 'type' => 'style', ]; } } } /** * Public helper to enqueue a script that has already been registered. * This ensures the plugin prefix is correctly applied. * * @param string $handle The non-prefixed handle of the script. * * @return void */ public function enqueue_script( string $handle ): void { wp_enqueue_script( $this->prefix_it( $handle ) ); } /** * Public helper to enqueue a style that has already been registered. * This ensures the plugin prefix is correctly applied. * * @param string $handle The non-prefixed handle of the style. * * @return void */ public function enqueue_style( string $handle ): void { wp_enqueue_style( $this->prefix_it( $handle ) ); } /** * Helper to prefix a handle string. * * @param string $handle The short handle. * * @return string The fully prefixed handle (e.g., 'adpresso-admin-main'). */ private function prefix_it( string $handle ): string { return 'adpresso-' . $handle; } }