353 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * Move JQuery from head to body.
 | 
						|
 *
 | 
						|
 * @package Builder
 | 
						|
 */
 | 
						|
 | 
						|
if ( ! defined( 'ABSPATH' ) ) {
 | 
						|
	exit; // Exit if accessed directly.
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Class to move JQuery from head to body.
 | 
						|
 */
 | 
						|
class ET_Builder_JQuery_Body {
 | 
						|
	/**
 | 
						|
	 * Cache.
 | 
						|
	 *
 | 
						|
	 * @var array.
 | 
						|
	 */
 | 
						|
	protected $_has_jquery_dep = [];
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Should move jquery.
 | 
						|
	 *
 | 
						|
	 * @var bool|null.
 | 
						|
	 */
 | 
						|
	protected static $_should_move_jquery = null;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Instance of `ET_Builder_JQuery_Body`.
 | 
						|
	 *
 | 
						|
	 * @var ET_Builder_JQuery_Body
 | 
						|
	 */
 | 
						|
	private static $_instance;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * ET_Builder_JQuery_Body constructor.
 | 
						|
	 */
 | 
						|
	public function __construct() {
 | 
						|
		add_action( 'wp_print_scripts', [ $this, 'wp_print_scripts' ], 99 );
 | 
						|
 | 
						|
		add_action( 'wp_head', [ $this, 'add_jquery_sub' ], 1 );
 | 
						|
		add_action( 'wp_enqueue_scripts', [ $this, 'remove_jquery_sub' ] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get post content from Theme Builder templates.
 | 
						|
	 * Combine it with the post content from the current post and integration code.
 | 
						|
	 *
 | 
						|
	 * @return string
 | 
						|
	 * @since 4.10.0
 | 
						|
	 */
 | 
						|
	public function get_all_content() {
 | 
						|
		static $all_content;
 | 
						|
 | 
						|
		// Cache the value.
 | 
						|
		if ( ! empty( $all_content ) ) {
 | 
						|
			return $all_content;
 | 
						|
		}
 | 
						|
 | 
						|
		global $post, $shortname;
 | 
						|
 | 
						|
		$all_content = empty( $post ) ? '' : $post->post_content;
 | 
						|
		$tb_layouts  = et_theme_builder_get_template_layouts();
 | 
						|
 | 
						|
		// Add TB templates content.
 | 
						|
		if ( ! empty( $tb_layouts ) ) {
 | 
						|
			$post_types = [
 | 
						|
				ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE,
 | 
						|
				ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE,
 | 
						|
				ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE,
 | 
						|
			];
 | 
						|
 | 
						|
			foreach ( $post_types as $post_type ) {
 | 
						|
				$layout = $tb_layouts[ $post_type ];
 | 
						|
				if ( $layout['override'] && ! empty( $layout['enabled'] ) ) {
 | 
						|
					$template     = get_post( intval( $layout['id'] ) );
 | 
						|
					$all_content .= $template->post_content;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		$integrations = [
 | 
						|
			'head',
 | 
						|
			'body',
 | 
						|
			'single_top',
 | 
						|
			'single_bottom',
 | 
						|
		];
 | 
						|
 | 
						|
		// Add Integration code.
 | 
						|
		foreach ( $integrations as $integration ) {
 | 
						|
			$all_content .= et_get_option( $shortname . '_integration_' . $integration );
 | 
						|
		}
 | 
						|
 | 
						|
		return $all_content;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Recursively check if a script (or its deps) depends on JQuery.
 | 
						|
	 *
 | 
						|
	 * @since 4.10.0
 | 
						|
	 * @param string $script Script Handle.
 | 
						|
	 *
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	public function has_jquery_dep( $script ) {
 | 
						|
		global $wp_scripts;
 | 
						|
 | 
						|
		$registered = $wp_scripts->registered;
 | 
						|
		$handles    = [ $script ];
 | 
						|
		$stack      = [];
 | 
						|
		$result     = false;
 | 
						|
 | 
						|
		while ( false === $result && $handles ) {
 | 
						|
			foreach ( $handles as $handle ) {
 | 
						|
				if ( ! empty( $this->_has_jquery_dep[ $handle ] ) ) {
 | 
						|
					$result = true;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if ( isset( $registered[ $handle ] ) ) {
 | 
						|
					$deps = $registered[ $handle ]->deps;
 | 
						|
					if ( $deps ) {
 | 
						|
						if ( in_array( 'jquery-core', $deps, true ) ) {
 | 
						|
							$result = true;
 | 
						|
							break;
 | 
						|
						}
 | 
						|
						array_push( $stack, $deps );
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			$handles = array_pop( $stack );
 | 
						|
		}
 | 
						|
 | 
						|
		$this->_has_jquery_dep[ $script ] = $result;
 | 
						|
		return $result;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get script deps.
 | 
						|
	 *
 | 
						|
	 * @since 4.10.0
 | 
						|
	 * @param string $script Script Handle.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_deps( $script ) {
 | 
						|
		global $wp_scripts;
 | 
						|
 | 
						|
		$registered = $wp_scripts->registered;
 | 
						|
		$handles    = is_array( $script ) ? $script : [ $script ];
 | 
						|
		$all_deps   = $handles;
 | 
						|
		$stack      = [];
 | 
						|
		$done       = [];
 | 
						|
 | 
						|
		while ( $handles ) {
 | 
						|
			foreach ( $handles as $handle ) {
 | 
						|
				if ( ! isset( $done[ $handle ] ) && isset( $registered[ $handle ] ) ) {
 | 
						|
					$deps = $registered[ $handle ]->deps;
 | 
						|
					if ( $deps ) {
 | 
						|
						$all_deps = array_merge( $all_deps, $deps );
 | 
						|
						array_push( $stack, $deps );
 | 
						|
					}
 | 
						|
					$done[ $handle ] = true;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			$handles = array_pop( $stack );
 | 
						|
		}
 | 
						|
 | 
						|
		return array_unique( $all_deps );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Check if a script is currently enqueued in HEAD.
 | 
						|
	 *
 | 
						|
	 * @since 4.10.0
 | 
						|
	 * @param string $handle Script Handle.
 | 
						|
	 *
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	public function in_head( $handle ) {
 | 
						|
		global $wp_scripts;
 | 
						|
 | 
						|
		if ( empty( $wp_scripts->registered[ $handle ] ) ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		$script = $wp_scripts->registered[ $handle ];
 | 
						|
 | 
						|
		if ( isset( $script->args ) && 1 === $script->args ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		if ( isset( $script->extra['group'] ) && 1 === $script->extra['group'] ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Check whether some content includes jQuery or not.
 | 
						|
	 *
 | 
						|
	 * @since 4.10.0
 | 
						|
	 * @param string $content Content.
 | 
						|
	 *
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	public static function has_jquery_content( $content ) {
 | 
						|
		$has_jquery_regex = '/\(jQuery|jQuery\.|jQuery\)/';
 | 
						|
		return 1 === preg_match( $has_jquery_regex, $content );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Move jQuery / migrate / 3P scripts in BODY.
 | 
						|
	 *
 | 
						|
	 * @since 4.10.0
 | 
						|
	 *
 | 
						|
	 * @return void
 | 
						|
	 */
 | 
						|
	public function wp_print_scripts() {
 | 
						|
		global $shortname;
 | 
						|
 | 
						|
		if ( $this->should_move_jquery() ) {
 | 
						|
			global $wp_scripts;
 | 
						|
			$queue = $this->get_deps( $wp_scripts->queue );
 | 
						|
			$head  = in_array( 'jquery-migrate', $queue, true ) ? [ 'jquery-migrate' ] : [];
 | 
						|
			$mode  = 'on' === et_get_option( $shortname . '_enable_jquery_body_super' ) ? 'super' : 'safe';
 | 
						|
 | 
						|
			// Find all head scripts that depends on JQuery.
 | 
						|
			foreach ( $queue as $handle ) {
 | 
						|
				if ( $this->in_head( $handle ) && $this->has_jquery_dep( $handle ) ) {
 | 
						|
					if ( 'safe' === $mode && 'jquery' !== $handle && 'jquery-migrate' !== $handle ) {
 | 
						|
						// Bail out when a script requiring jQuery is found in head.
 | 
						|
						return;
 | 
						|
					}
 | 
						|
					$head[] = $handle;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			$registered = $wp_scripts->registered;
 | 
						|
 | 
						|
			// Disable the feature when finding a script which does not depend on jQuery
 | 
						|
			// but still uses it inside its inlined content (before/after).
 | 
						|
			foreach ( $queue as $handle ) {
 | 
						|
				if ( ! $this->has_jquery_dep( $handle ) ) {
 | 
						|
					$script = $registered[ $handle ];
 | 
						|
					$data   = '';
 | 
						|
 | 
						|
					if ( isset( $script->extra['data'] ) ) {
 | 
						|
						$data .= $script->extra['data'];
 | 
						|
					}
 | 
						|
					if ( isset( $script->extra['before'] ) ) {
 | 
						|
						$data .= join( '', $script->extra['before'] );
 | 
						|
					}
 | 
						|
					if ( isset( $script->extra['after'] ) ) {
 | 
						|
						$data .= join( '', $script->extra['after'] );
 | 
						|
					}
 | 
						|
 | 
						|
					if ( ! empty( $data ) && self::has_jquery_content( $data ) ) {
 | 
						|
						return;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Disable the feature when jQuery is found in TB/Post Content.
 | 
						|
			if ( self::has_jquery_content( $this->get_all_content() ) ) {
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( ! empty( $head ) ) {
 | 
						|
				foreach ( $this->get_deps( $head ) as $handle ) {
 | 
						|
					$wp_scripts->add_data( $handle, 'group', 1 );
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the class instance.
 | 
						|
	 *
 | 
						|
	 * @since 4.10.0
 | 
						|
	 *
 | 
						|
	 * @return ET_Builder_JQuery_Body
 | 
						|
	 */
 | 
						|
	public static function instance() {
 | 
						|
		if ( ! self::$_instance ) {
 | 
						|
			self::$_instance = new self();
 | 
						|
		}
 | 
						|
 | 
						|
		return self::$_instance;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Fake jQuery in head when jQuery body option is true.
 | 
						|
	 */
 | 
						|
	public function add_jquery_sub() {
 | 
						|
		global $shortname;
 | 
						|
 | 
						|
		$jquery_compatibility_enabled = 'on' === et_get_option( $shortname . '_enable_jquery_compatibility', 'on' ) ? true : false;
 | 
						|
 | 
						|
		if ( $this->should_move_jquery() && $jquery_compatibility_enabled ) {
 | 
						|
			echo '<script type="text/javascript">
 | 
						|
			let jqueryParams=[],jQuery=function(r){return jqueryParams=[...jqueryParams,r],jQuery},$=function(r){return jqueryParams=[...jqueryParams,r],$};window.jQuery=jQuery,window.$=jQuery;let customHeadScripts=!1;jQuery.fn=jQuery.prototype={},$.fn=jQuery.prototype={},jQuery.noConflict=function(r){if(window.jQuery)return jQuery=window.jQuery,$=window.jQuery,customHeadScripts=!0,jQuery.noConflict},jQuery.ready=function(r){jqueryParams=[...jqueryParams,r]},$.ready=function(r){jqueryParams=[...jqueryParams,r]},jQuery.load=function(r){jqueryParams=[...jqueryParams,r]},$.load=function(r){jqueryParams=[...jqueryParams,r]},jQuery.fn.ready=function(r){jqueryParams=[...jqueryParams,r]},$.fn.ready=function(r){jqueryParams=[...jqueryParams,r]};</script>';
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Disable the Fake jQuery in head when jQuery body option is true.
 | 
						|
	 */
 | 
						|
	public function remove_jquery_sub() {
 | 
						|
		global $shortname;
 | 
						|
 | 
						|
		$jquery_compatibility_enabled = 'on' === et_get_option( $shortname . '_enable_jquery_compatibility', 'on' ) ? true : false;
 | 
						|
 | 
						|
		if ( $this->should_move_jquery() && $jquery_compatibility_enabled ) {
 | 
						|
			$script = 'jqueryParams.length&&$.each(jqueryParams,function(e,r){if("function"==typeof r){var n=String(r);n.replace("$","jQuery");var a=new Function("return "+n)();$(document).ready(a)}});';
 | 
						|
 | 
						|
			wp_add_inline_script( 'jquery', $script, 'after' );
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Check if jQuery should be moved to the body.
 | 
						|
	 */
 | 
						|
	public function should_move_jquery() {
 | 
						|
		global $shortname;
 | 
						|
 | 
						|
		if ( null === self::$_should_move_jquery ) {
 | 
						|
			/**
 | 
						|
			 * Filters whether JQuery Body feature is enabled or not.
 | 
						|
			 *
 | 
						|
			 * @since 4.10.5
 | 
						|
			 *
 | 
						|
			 * @param bool $enabled JQuery Body enabled value.
 | 
						|
			 * @param string $content TB/Post Content.
 | 
						|
			 */
 | 
						|
			if ( false === apply_filters( 'et_builder_enable_jquery_body', true, $this->get_all_content() ) ) {
 | 
						|
				// Bail out if disabled by filter.
 | 
						|
				self::$_should_move_jquery = false;
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
			self::$_should_move_jquery = ! ( is_admin() || wp_doing_ajax() || et_is_builder_plugin_active() || is_customize_preview() || is_et_pb_preview() || 'wp-login.php' === $GLOBALS['pagenow'] || et_fb_is_enabled() ) && 'on' === et_get_option( $shortname . '_enable_jquery_body', 'on' );
 | 
						|
		}
 | 
						|
 | 
						|
		return self::$_should_move_jquery;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
ET_Builder_JQuery_Body::instance();
 |