: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// Failed for some other reason. There's no point in continuing
self::$_can_write = $can_continue = false;
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
define( 'DONOTCACHEPAGE', true );
* Enqueues static files for an output location if available.
* @param string $location {@link self::$_OUTPUT_LOCATIONS}
protected static function _maybe_enqueue_static_resources( $location ) {
$sorted_resources = self::get_resources_by_output_location( $location );
foreach ( $sorted_resources as $priority => $resources ) {
foreach ( $resources as $slug => $resource ) {
if ( $resource->disabled ) {
// Resource is disabled. Remove it from the queue.
self::_unassign_output_location( $location, $resource );
if ( $resource->forced_inline || ! $resource->URL || ! $resource->has_file() ) {
if ( 'style' === $resource->type ) {
self::_enqueue_style( $resource );
} else if ( 'script' === $resource->type ) {
self::_enqueue_script( $resource );
if ( $resource->enqueued ) {
self::_unassign_output_location( $location, $resource );
* Outputs all non-enqueued resources for an output location inline.
* @param string $location {@link self::$_OUTPUT_LOCATIONS}
protected static function _maybe_output_inline_resources( $location ) {
$sorted_resources = self::get_resources_by_output_location( $location );
foreach ( $sorted_resources as $priority => $resources ) {
foreach ( $resources as $slug => $resource ) {
if ( $resource->disabled ) {
// Resource is disabled. Remove it from the queue.
self::_unassign_output_location( $location, $resource );
$data = $resource->get_data( 'inline' );
$same_write_file_location = $resource->write_file_location === $resource->location;
if ( empty( $data ) && 'footer' !== $location && $same_write_file_location ) {
// This resource doesn't have any data yet so we'll assign it to the next output location.
$next_location = self::_get_next_output_location();
$resource->set_output_location( $next_location );
} else if ( empty( $data ) ) {
'<%1$s id="%2$s">%3$s</%1$s>',
esc_html( $resource->type ),
esc_attr( $resource->slug ),
et_core_esc_previously( wp_strip_all_tags( $data ) )
if ( $same_write_file_location ) {
// File wasn't created during this location's callback and it won't be created later
$resource->inlined = true;
* Registers necessary callbacks.
protected static function _register_callbacks() {
$class = 'ET_Core_PageResource';
// Output Location: head-early, right after theme styles have been enqueued.
add_action( 'wp_enqueue_scripts', array( $class, 'head_early_output_cb' ), 11 );
// Output Location: head, right BEFORE the theme and wp's custom css.
add_action( 'wp_head', array( $class, 'head_output_cb' ), 99 );
// Output Location: head-late, right AFTER the theme and wp's custom css.
add_action( 'wp_head', array( $class, 'head_late_output_cb' ), 103 );
// Output Location: footer
add_action( 'wp_footer', array( $class, 'footer_output_cb' ), 20 );
// Always delete cached resources for a post upon saving.
add_action( 'save_post', array( $class, 'save_post_cb' ), 10, 3 );
// Always delete cached resources for theme customizer upon saving.
add_action( 'customize_save_after', array( $class, 'customize_save_after_cb') );
// Add fallback callbacks (lol) to link/script tags
add_filter( 'style_loader_tag', array( $class, 'link_and_script_tags_filter_cb' ), 999, 2 );
* Initializes the WPFilesystem class.
protected static function _setup_wp_filesystem() {
// The wpfs instance will always exists at this point because the cache dir class initializes it beforehand
self::$wpfs = $GLOBALS['wp_filesystem'];
* Unassign a resource from an output location.
* @param string $location {@link self::$_OUTPUT_LOCATIONS}
* @param ET_Core_PageResource $resource
protected static function _unassign_output_location( $location, $resource ) {
unset( self::$_resources_by_location[ $location ][ $resource->priority ][ $resource->slug ] );
protected static function _validate_property( $property, $value ) {
'location' => self::$_OUTPUT_LOCATIONS,
'owner' => self::$_OWNERS,
$value = et_()->normalize_path( realpath( $value ) );
$is_valid = et_()->starts_with( $value, et_core_cache_dir()->path );
$base_url = et_core_cache_dir()->url;
$is_valid = et_()->starts_with( $value, set_url_scheme( $base_url, 'http' ) );
$is_valid = $is_valid ? $is_valid : et_()->starts_with( $value, set_url_scheme( $base_url, 'https' ) );
$is_valid = 'global' === $value || 'all' === $value || is_numeric( $value );
$is_valid = isset( $valid_values[ $property ] ) && in_array( $value, $valid_values[ $property ] );
return $is_valid ? $value : '';
* Whether or not we are able to write to the filesystem.
public static function can_write_to_filesystem() {
return et_core_cache_dir()->can_write;
* Output Location: footer
* {@see 'wp_footer' (20) Allow third-party extensions some room to do what they do}
public static function footer_output_cb() {
self::_maybe_create_static_resources( 'footer' );
self::_maybe_enqueue_static_resources( 'footer' );
self::_maybe_output_inline_resources( 'footer' );
* Returns the absolute path to our cache directory.
* @since 4.0.8 Removed `$path_type` param b/c cache directory might not be located under wp-content.
public static function get_cache_directory() {
return et_core_cache_dir()->path;
* Returns all current resources.
* @return array {@link self::$_resources}
public static function get_resources() {
return self::$_resources;
* Returns the current resources for the provided output location, sorted by priority.
* @param string $location The desired output location {@see self::$_OUTPUT_LOCATIONS}.
* @type ET_Core_PageResource[] $priority {
* @type ET_Core_PageResource $slug Resource.
public static function get_resources_by_output_location( $location ) {
return self::$_resources_by_location[ $location ];
* Returns the current resources for the provided scope.
* @param string $scope The desired scope (post|global).
* @return ET_Core_PageResource[]
public static function get_resources_by_scope( $scope ) {
return self::$_resources_by_scope[ $scope ];
* Output Location: head-early
* {@see 'wp_enqueue_scripts' (11) Should run right after the theme enqueues its styles.}
public static function head_early_output_cb() {
self::_maybe_create_static_resources( 'head-early' );
self::_maybe_enqueue_static_resources( 'head-early' );
self::_maybe_output_inline_resources( 'head-early' );
* {@see 'wp_head' (99) Must run BEFORE the theme and WP's custom css callbacks.}
public static function head_output_cb() {
self::_maybe_create_static_resources( 'head' );
self::_maybe_enqueue_static_resources( 'head' );
self::_maybe_output_inline_resources( 'head' );
* Output Location: head-late
* {@see 'wp_head' (103) Must run AFTER the theme and WP's custom css callbacks.}
public static function head_late_output_cb() {
self::_maybe_create_static_resources( 'head-late' );
self::_maybe_enqueue_static_resources( 'head-late' );
self::_maybe_output_inline_resources( 'head-late' );
* Adds fallback handlers to the link and script tags of our page resources.
* {@see 'style_loader_tag'}
* {@see 'script_loader_tag'}
public static function link_and_script_tags_filter_cb( $tag, $handle ) {
if ( ! isset( self::$_resources[ $handle ] ) ) {
if ( function_exists( 'et_get_option' ) && 'off' === et_get_option( 'et_pb_static_css_file', 'on' ) ) {
/** @see ET_Core_SupportCenter::toggle_safe_mode */
if ( et_core_is_safe_mode_active() ) {
$existing_onerror = "/(?<=onerror=')(.*?)(;?')/";
$existing_onload = "/(?<=onload=')(.*?)(;?')/"; // Internet Explorer :face_with_rolling_eyes:
$onerror_callback = self::$_onerror;
$onload_callback = self::$_onload;
$onerror_replacement = $onerror_callback . ';$1';
$onload_replacement = $onload_callback . ';$1';
$tag = preg_replace( $existing_onerror, $onerror_replacement, $tag, 1, $onerror_replaced_count );
$tag = preg_replace( $existing_onload, $onload_replacement, $tag, 1, $onload_replaced_count );
if ( 1 === $onerror_replaced_count && 1 === $onload_replaced_count ) {
if ( 1 !== $onerror_replaced_count ) {
$tag = str_replace( '/>', "onerror='{$onerror_callback}' />", $tag );
if ( 1 !== $onload_replaced_count ) {
$tag = str_replace( '/>', "onload='{$onload_callback}' />", $tag );
* {@see 'customize_save_after'}
* @param WP_Customize_Manager $manager
public static function customize_save_after_cb( $manager ) {
self::remove_static_resources( 'all', 'all' );
public static function save_post_cb( $post_id, $post, $update ) {
if ( ! $update || ! function_exists( 'et_builder_enabled_for_post' ) ) {
if ( ! et_builder_enabled_for_post( $post_id ) ) {
self::remove_static_resources( $post_id, 'all' );
* Remove static resources for a post, or optionally all resources, if any exist.
* @param string|int $post_id
public static function remove_static_resources( $post_id, $owner = 'core', $force = false ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
if ( ! wp_doing_cron() && ! et_core_security_check_passed( 'edit_posts' ) ) {
if ( ! self::can_write_to_filesystem() ) {
if ( ! self::$data_utils ) {
$post_id = self::_validate_property( 'post_id', $post_id );
$owner = self::_validate_property( 'owner', $owner );
if ( '' === $owner || '' === $post_id ) {
$_post_id = 'all' === $post_id ? '*' : $post_id;
$_owner = 'all' === $owner ? '*' : $owner;
$cache_dir = self::get_cache_directory();
(array) glob( "{$cache_dir}/et-{$_owner}-*" ),
(array) glob( "{$cache_dir}/{$_post_id}/et-{$_owner}-*" ),
(array) glob( "{$cache_dir}/*/et-{$_owner}-*-tb-{$_post_id}-*" ),
(array) glob( "{$cache_dir}/*/et-{$_owner}-*-tb-for-{$_post_id}-*" )
foreach( (array) $files as $file ) {
$file = self::$data_utils->normalize_path( $file );
if ( ! et_()->starts_with( $file, $cache_dir ) ) {
// File is not located inside cache directory so skip it.
if ( is_file( $file ) ) {
self::$wpfs->delete( $file );
// Remove empty directories
self::$data_utils->remove_empty_directories( $cache_dir );
// Clear cache managed by 3rd-party cache plugins
$post_id = ! empty( $post_id ) && absint( $post_id ) > 0 ? $post_id : '';
et_core_clear_wp_cache( $post_id );
// Set our DONOTCACHEPAGE file for the next request.
self::$data_utils->ensure_directory_exists( $cache_dir );
self::$wpfs->put_contents( $cache_dir . '/DONOTCACHEPAGE', '' );
delete_option( 'et_core_page_resource_remove_all' );
public static function wpfs() {
if ( null !== self::$wpfs ) {
return self::$wpfs = et_core_cache_dir()->wpfs;
protected function _initialize_resource() {
if ( ! self::can_write_to_filesystem() ) {
$this->BASE_DIR = $this->TEMP_DIR = $this->PATH = $this->URL = '';
$this->_register_resource();
$file_extension = 'style' === $this->type ? '.min.css' : '.min.js';
$path = self::get_cache_directory();
$url = et_core_cache_dir()->url;
$files = glob( $path . "/{$this->post_id}/{$this->filename}-[0-9]*{$file_extension}" );
// Static resource file exists
$file = array_pop( $files );
$this->PATH = self::$data_utils->normalize_path( $file );
$this->BASE_DIR = dirname( $this->PATH );
$this->URL = et_()->path( $url, $this->post_id, basename( $this->PATH ) );
// There are multiple files for this resource. Let's delete the extras.
foreach ( $files as $extra_file ) {
ET_Core_Logger::debug( 'Removing extra page resource file: ' . $extra_file );
@self::$wpfs->delete( $extra_file );
// Static resource file doesn't exist
$time = self::$_request_time;
$url .= "/{$this->post_id}/{$this->filename}-{$time}{$file_extension}";
$path .= "/{$this->post_id}/{$this->filename}-{$time}{$file_extension}";
$this->BASE_DIR = self::$data_utils->normalize_path( dirname( $path ) );
$this->TEMP_DIR = $this->BASE_DIR . "/{$this->slug}~";
$this->_register_resource();
protected function _register_resource() {
$scope = 'global' === $this->post_id ? 'global' : 'post';
self::$_resources[ $this->slug ] = $this;
self::$_resources_by_scope[ $scope ][ $this->slug ] = $this;
self::_assign_output_location( $this->location, $this );
public function get_data( $context ) {
ksort( $this->data, SORT_NUMERIC );
* Filters the resource's data array.
* @type string[] $priority Resource data.
* @param string $context Where the data will be used. Accepts 'inline', 'file'.
* @param ET_Core_PageResource $resource The resource instance.