'; /** @var array outputs that will render on this request, keyed by post id */ private static $docs = array(); private static $modules = array(); private static $printed_css = array(); private static $enqueued = false; private static $stack = array(); public static function init() { add_filter( 'the_content', array( __CLASS__, 'content_early' ), 1 ); add_filter( 'the_content', array( __CLASS__, 'content_late' ), PHP_INT_MAX ); add_action( 'wp', array( __CLASS__, 'collect' ), 20 ); add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue' ), 20 ); add_action( 'wp_enqueue_scripts', array( __CLASS__, 'drop_elementor_assets' ), 9999 ); add_action( 'wp_print_styles', array( __CLASS__, 'drop_elementor_assets' ), 1 ); add_action( 'wp_print_footer_scripts', array( __CLASS__, 'drop_elementor_assets' ), 1 ); add_action( 'wp_enqueue_scripts', array( __CLASS__, 'head' ), 1 ); add_action( 'wp_footer', array( __CLASS__, 'footer_modules' ), 5 ); add_filter( 'body_class', array( __CLASS__, 'body_class' ) ); add_filter( 'template_include', array( __CLASS__, 'page_templates' ), 99 ); add_action( 'init', array( __CLASS__, 'register_page_templates' ), 30 ); add_action( 'template_redirect', array( __CLASS__, 'protect_templates' ) ); add_action( 'template_redirect', array( __CLASS__, 'unlock' ), 1 ); add_action( 'template_redirect', array( Conditions::class, 'track' ), 2 ); add_action( 'admin_bar_menu', array( __CLASS__, 'admin_bar' ), 81 ); add_filter( 'get_the_excerpt', array( __CLASS__, 'excerpt' ), 5, 2 ); add_filter( 'script_loader_tag', array( __CLASS__, 'defer_modules' ), 10, 2 ); } // ------------------------------------------------------------------ // Content // ------------------------------------------------------------------ public static function content_early( $content ) { $id = get_the_ID(); if ( $id && Editor::canvas_id() === $id && ! self::$stack ) { return self::TOKEN; } if ( ! $id || ! Document::is_built( $id ) || post_password_required( $id ) ) { return $content; } if ( in_array( $id, self::$stack, true ) ) { return $content; // a template rendering its own post-content } return self::TOKEN; } public static function content_late( $content ) { if ( false === strpos( $content, self::TOKEN ) ) { return $content; } $id = get_the_ID(); if ( Editor::canvas_id() === $id ) { return str_replace( self::TOKEN, '
', $content ); } return str_replace( self::TOKEN, self::render_post( $id ), $content ); } public static function is_preview( $post_id ) { return isset( $_GET['nx_preview'], $_GET['_nxp'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_nxp'] ) ), 'nx_preview_' . $post_id ) && Plugin::can_build( $post_id ); } /** Render a built post/template (runtime placeholders resolved). */ public static function render_post( $post_id, $wrap_class = '' ) { $post_id = (int) $post_id; if ( in_array( $post_id, self::$stack, true ) || count( self::$stack ) > 8 ) { return ''; } $out = Document::get_out( $post_id, self::is_preview( $post_id ) ); if ( ! $out ) { return ''; } self::$stack[] = $post_id; self::ensure_assets( $post_id, $out ); // Escaped late: the stored layout goes through the same KSES allow-list as on save; the dynamic parts that // Runtime inserts (menus, post lists, forms…) are escaped by the functions that build them. $html = Runtime::process( Sanitize::html( $out['html'] ), $out, $post_id ); if ( function_exists( 'wp_filter_content_tags' ) && false !== strpos( $html, 'wp-image-' ) ) { // Responsive srcset/sizes, intrinsic size and position-aware lazy/fetchpriority, exactly like core content. $html = wp_filter_content_tags( $html, 'the_content' ); } array_pop( self::$stack ); if ( $wrap_class ) { $html = '
' . $html . '
'; } return $html; } public static function current_stack() { return self::$stack; } // ------------------------------------------------------------------ // Assets: decide early (on `wp`) what this request renders. // ------------------------------------------------------------------ public static function collect() { if ( is_admin() ) { return; } $ids = array(); if ( is_singular() ) { $qid = get_queried_object_id(); if ( Document::is_built( $qid ) ) { $ids[] = $qid; } } foreach ( Theme_Builder::active_templates() as $tid ) { $ids[] = $tid; } foreach ( Popups::active() as $pid ) { $ids[] = $pid; } $canvas = Editor::canvas_id(); foreach ( array_unique( $ids ) as $id ) { if ( $id === $canvas ) { continue; } $out = Document::get_out( $id, self::is_preview( $id ) ); if ( $out ) { self::$docs[ $id ] = $out; foreach ( (array) ( $out['tpl'] ?? array() ) as $tpl ) { $o = Document::get_out( (int) $tpl ); if ( $o ) { self::$docs[ (int) $tpl ] = $o; } } } } } public static function unlock() { if ( self::$docs ) { Runtime::maybe_unlock( self::$docs ); } } public static function enqueue() { self::register_modules(); $global = Document::global_css_url(); $any = self::$docs || Editor::canvas_id(); if ( ! $any ) { return; } if ( $global ) { wp_enqueue_style( 'akram-global', $global, array(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- the URL already carries a content version. } else { wp_register_style( 'akram-global', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- the URL already carries a content version. wp_enqueue_style( 'akram-global' ); wp_add_inline_style( 'akram-global', Document::global_css() ); } $fonts = (array) get_option( 'akram_global_fonts', array() ); if ( ! $fonts && ! get_option( 'akram_global_css' ) ) { $fonts = array( 'Bricolage Grotesque' => array( 400, 600, 700, 800 ), 'Figtree' => array( 400, 500, 600, 700 ), ); // default design system } foreach ( self::$docs as $id => $out ) { self::enqueue_doc_css( $id, $out ); foreach ( (array) ( $out['fonts'] ?? array() ) as $fam => $w ) { $fonts[ $fam ] = array_unique( array_merge( $fonts[ $fam ] ?? array(), (array) $w ) ); } foreach ( (array) ( $out['modules'] ?? array() ) as $m ) { self::$modules[ $m ] = true; } } if ( Popups::active() ) { self::$modules['popup'] = true; } Fonts::enqueue( $fonts ); if ( Editor::canvas_id() ) { // The editor injects its own styles; theme + fonts still load normally. Fonts::enqueue( $fonts ); } self::$enqueued = true; if ( Options::get( 'disable_block_css' ) && self::$docs && is_singular() && Document::is_built( get_queried_object_id() ) && self::block_css_unused() ) { wp_dequeue_style( 'wp-block-library' ); wp_dequeue_style( 'wp-block-library-theme' ); wp_dequeue_style( 'classic-theme-styles' ); } } /** * While Elementor is still active (e.g. mid-migration) it loads its global CSS, kit CSS and Google Fonts * on every page. An Akram-built page uses none of it. Elementor Pro's theme builder (headers/footers) needs * them, so this only runs without Elementor Pro. */ public static function drop_elementor_assets() { if ( ! defined( 'ELEMENTOR_VERSION' ) || defined( 'ELEMENTOR_PRO_VERSION' ) || ! is_singular() || Editor::canvas_id() ) { return; } $id = get_queried_object_id(); if ( ! Document::is_built( $id ) || 'builder' === get_post_meta( $id, '_elementor_edit_mode', true ) || ! apply_filters( 'akram_drop_elementor_assets', true, $id ) ) { return; } foreach ( array( wp_styles(), wp_scripts() ) as $deps ) { foreach ( (array) $deps->queue as $handle ) { if ( preg_match( '/^elementor-(frontend|post-\d+|gf-[a-z0-9-]+|icons(-[a-z0-9-]+)?|webpack-runtime|frontend-modules|waypoints|common)$/', $handle ) || 0 === strpos( $handle, 'e-animation' ) || 0 === strpos( $handle, 'widget-' ) ) { $deps instanceof \WP_Styles ? wp_dequeue_style( $handle ) : wp_dequeue_script( $handle ); } } } } /** * The ~140 KB block-library stylesheet is dead weight on an Akram page with a classic theme, * unless the theme's sidebars/footer use block widgets. Block themes load per-block CSS on demand, * so they are left alone. */ private static function block_css_unused() { if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) { return false; } $blocks = get_option( 'widget_block' ); if ( is_array( $blocks ) ) { foreach ( (array) get_option( 'sidebars_widgets', array() ) as $area => $widgets ) { if ( 'wp_inactive_widgets' === $area || ! is_array( $widgets ) ) { continue; } foreach ( $widgets as $w ) { if ( 0 === strpos( (string) $w, 'block-' ) ) { return false; } } } } return (bool) apply_filters( 'akram_remove_block_css', true ); } private static function enqueue_doc_css( $id, $out ) { if ( isset( self::$printed_css[ $id ] ) ) { return; } self::$printed_css[ $id ] = true; if ( ! empty( $out['draft'] ) ) { wp_register_style( 'akram-' . $id, false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- the URL already carries a content version. wp_enqueue_style( 'akram-' . $id ); wp_add_inline_style( 'akram-' . $id, $out['css'] ); return; } $url = Document::css_url( $id ); if ( $url ) { wp_enqueue_style( 'akram-' . $id, $url, array( 'akram-global' ), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- the URL already carries a content version. } else { wp_register_style( 'akram-' . $id, false, array( 'akram-global' ) ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- the URL already carries a content version. wp_enqueue_style( 'akram-' . $id ); wp_add_inline_style( 'akram-' . $id, Document::css( $id ) ); } } /** Late discovery (shortcodes, loop templates, nested templates): print styles inline once. */ public static function ensure_assets( $id, $out ) { foreach ( (array) ( $out['modules'] ?? array() ) as $m ) { self::$modules[ $m ] = true; } if ( isset( self::$printed_css[ $id ] ) || Editor::canvas_id() === $id ) { return; } if ( ! did_action( 'wp_head' ) || ! self::$enqueued ) { self::$docs[ $id ] = $out; if ( did_action( 'wp_enqueue_scripts' ) ) { self::enqueue_doc_css( $id, $out ); } return; } self::$printed_css[ $id ] = true; $url = empty( $out['draft'] ) ? Document::css_url( $id ) : null; self::print_style( 'akram-' . (int) $id, (string) $url, $url ? '' : ( ! empty( $out['draft'] ) ? $out['css'] : Document::css( $id ) ) ); if ( ! wp_style_is( 'akram-global', 'done' ) && ! wp_style_is( 'akram-global', 'enqueued' ) ) { self::print_style( 'akram-global-inline', '', Document::global_css() ); } Fonts::print_late( (array) ( $out['fonts'] ?? array() ) ); } /** * Prints one stylesheet (or an inline style block) through the WordPress styles API, also after wp_head * (late-discovered documents, standalone screens). CSS passed inline was sanitised on save. */ public static function print_style( $handle, $url = '', $inline = '' ) { if ( wp_style_is( $handle, 'done' ) ) { return; } if ( $url ) { wp_register_style( $handle, $url, array(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- the URL already carries a content version. } else { wp_register_style( $handle, false, array(), AKRAM_VERSION ); wp_add_inline_style( $handle, $inline ); } wp_print_styles( $handle ); } public static function add_module( $name ) { self::$modules[ sanitize_key( $name ) ] = true; } public static function head() { if ( ! self::$docs && ! Editor::canvas_id() ) { return; } $needs_js_class = false; $dm = ''; foreach ( self::$docs as $out ) { if ( ! empty( $out['flags']['dm'] ) ) { $dm = (string) $out['flags']['dm']; } if ( ! empty( $out['anim'] ) || array_intersect( (array) ( $out['modules'] ?? array() ), array( 'motion', 'counter' ) ) ) { $needs_js_class = true; } } $js = ''; if ( $dm ) { // Apply the saved/system color theme before first paint (no flash of the wrong theme). $js .= "(function(){try{var m=localStorage.getItem('nx-theme')||" . wp_json_encode( $dm ) . ";if(m!=='light'&&m!=='dark')m=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';document.documentElement.dataset.nxTheme=m}catch(e){}})();"; } if ( $needs_js_class ) { $js .= "document.documentElement.classList.add('nx-js');"; } if ( $js ) { // Printed in the head by wp_head(), before first paint. wp_register_script( 'akram-head', false, array(), AKRAM_VERSION, false ); wp_enqueue_script( 'akram-head' ); wp_add_inline_script( 'akram-head', $js ); } } private static function register_modules() { $dir = AKRAM_DIR . 'assets/build/m/'; if ( ! is_dir( $dir ) ) { return; } foreach ( glob( $dir . '*.js' ) as $file ) { $name = basename( $file, '.js' ); if ( '.min' === substr( $name, -4 ) ) { continue; // the minified twin of a readable build; Plugin::build_url() picks which one loads } wp_register_script( 'akram-m-' . $name, Plugin::build_url( 'm/' . $name . '.js' ), array(), AKRAM_VERSION, array( 'in_footer' => true, 'strategy' => 'defer', ) ); } } public static function footer_modules() { if ( Editor::canvas_id() ) { return; // the editor loads every module into the canvas itself } if ( ! self::$modules ) { return; } $cfg = array( 'rest' => esc_url_raw( rest_url( Rest::NS . '/' ) ), 'assets' => AKRAM_URL . 'assets/', 'min' => Plugin::min_suffix(), 'home' => home_url( '/' ), 'tz' => (float) get_option( 'gmt_offset' ), ); $cap = Options::get( 'captcha' ); if ( 'recaptcha' === $cap && Options::get( 'recaptcha_site' ) ) { $cfg['captcha'] = array( 'type' => 'recaptcha', 'key' => Options::get( 'recaptcha_site' ), ); } elseif ( 'turnstile' === $cap && Options::get( 'turnstile_site' ) ) { $cfg['captcha'] = array( 'type' => 'turnstile', 'key' => Options::get( 'turnstile_site' ), ); } $first = true; foreach ( array_keys( self::$modules ) as $m ) { if ( ! wp_script_is( 'akram-m-' . $m, 'registered' ) ) { continue; } if ( $first ) { wp_add_inline_script( 'akram-m-' . $m, 'window.akramCfg=' . wp_json_encode( $cfg ) . ';', 'before' ); $first = false; } wp_enqueue_script( 'akram-m-' . $m ); } } public static function defer_modules( $tag, $handle ) { if ( 0 === strpos( $handle, 'akram-m-' ) && false === strpos( $tag, ' defer' ) && false === strpos( $tag, 'window.akramCfg' ) ) { $tag = str_replace( ' src=', ' defer src=', $tag ); } return $tag; } // ------------------------------------------------------------------ // Page templates // ------------------------------------------------------------------ public static function register_page_templates() { foreach ( Options::enabled_post_types() as $pt ) { add_filter( "theme_{$pt}_templates", array( __CLASS__, 'add_page_templates' ) ); } } public static function add_page_templates( $templates ) { $templates['akram-full'] = __( 'Akram Full Width', 'akram' ); $templates['akram-canvas'] = __( 'Akram Canvas (no header/footer)', 'akram' ); return $templates; } public static function page_templates( $template ) { if ( is_singular() ) { $slug = get_page_template_slug( get_queried_object_id() ); if ( 'akram-canvas' === $slug ) { return AKRAM_DIR . 'templates/canvas.php'; } if ( 'akram-full' === $slug ) { return AKRAM_DIR . 'templates/full-width.php'; } } return $template; } public static function body_class( $classes ) { if ( is_singular() && Document::is_built( get_queried_object_id() ) ) { $classes[] = 'nx-page'; } if ( Editor::canvas_id() ) { $classes[] = 'nx-editing'; } return $classes; } /** Templates are only viewable directly by builders (previewing). */ public static function protect_templates() { if ( is_singular( 'akram_template' ) && ! Plugin::can_build() ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); } } public static function admin_bar( $bar ) { if ( is_admin() || ! is_singular() ) { return; } $id = get_queried_object_id(); if ( ! $id || ! Plugin::can_build( $id ) || ! in_array( get_post_type( $id ), Options::enabled_post_types(), true ) ) { return; } $bar->add_node( array( 'id' => 'akram-edit', 'title' => '' . Brand::svg( '#5fd0e4', 18 ) . '' . esc_html__( 'Edit with Akram', 'akram' ), 'href' => Editor::edit_url( $id ), ) ); foreach ( Theme_Builder::active_templates() as $tid ) { $bar->add_node( array( 'parent' => 'akram-edit', 'id' => 'akram-edit-' . $tid, /* translators: 1: template type, 2: template title */ 'title' => esc_html( sprintf( __( '%1$s: %2$s', 'akram' ), Post_Types::type_labels()[ Post_Types::template_type( $tid ) ] ?? '', get_the_title( $tid ) ) ), 'href' => Editor::edit_url( $tid ), ) ); } } /** Builder pages have no meaningful raw excerpt: derive one from the static copy. */ public static function excerpt( $excerpt, $post = null ) { if ( $excerpt || ! $post || ! Document::is_built( $post->ID ) ) { return $excerpt; } // wp_strip_all_tags() also drops the contents of script and style elements (structured data). return wp_trim_words( wp_strip_all_tags( $post->post_content ), 40 ); } /** Purge popular page caches when content or design changes. */ public static function flush_caches( $post_id = 0 ) { if ( $post_id ) { clean_post_cache( $post_id ); } do_action( 'akram_flush_caches', $post_id ); if ( function_exists( 'rocket_clean_domain' ) ) { $post_id ? rocket_clean_post( $post_id ) : rocket_clean_domain(); } if ( function_exists( 'w3tc_flush_all' ) ) { w3tc_flush_all(); } if ( function_exists( 'wp_cache_clear_cache' ) ) { wp_cache_clear_cache(); } if ( class_exists( '\LiteSpeed\Purge' ) ) { do_action( 'litespeed_purge_all' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core or third-party hook. } if ( function_exists( 'sg_cachepress_purge_cache' ) ) { sg_cachepress_purge_cache(); } if ( class_exists( 'autoptimizeCache' ) ) { \autoptimizeCache::clearall(); } do_action( 'breeze_clear_all_cache' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core or third-party hook. do_action( 'cache_enabler_clear_complete_cache' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core or third-party hook. } }