This repository has been archived on 2022-06-23. You can view files and clone it, but cannot push or open issues or pull requests.
divi/includes/builder/feature/JQueryBody.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();