* Manages a frontend inline CSS or JavaScript resource.
* If possible, the resource will be served as a static file for better performance. It can be
* tied to a specific post or it can be 'global'. The resource can be output, static or inline,
* to one of four locations on the page:
* * `head-early`: right AFTER theme styles have been enqueued
* * `head` : right BEFORE the theme and wp's inline custom css
* * `head-late` : right AFTER the theme and wp's inline custom css
* * `footer` : in the footer
* The first time the class is instantiated, a static callback method will be registered for each
* output location. Inside each callback, we'll iterate over any/all instances that are assigned
* to the current output location and perform the following steps:
* 1. If a static file exists for the resource, go to the next step. Otherwise, try to create
* a static file for the resource if it has `data`. If it doesn't have `data`, assign it to
* the next output location and then move on to the next resource (continue).
* 2. If a static file exists for the resource, enqueue it (via WP or manually) and then move on
* to the next resource (continue). If no static file exists, go to the next step.
* 3. Output the resource inline.
class ET_Core_PageResource {
private static $_LOCK_FILE;
private static $_OUTPUT_LOCATIONS = array(
private static $_OWNERS = array(
private static $_SCOPES = array(
private static $_TEMP_DIRS = array();
private static $_TYPES = array(
* Whether or not we have write access to the filesystem.
private static $_can_write;
private static $_onerror = 'et_core_page_resource_fallback(this, true)';
private static $_onload = 'et_core_page_resource_fallback(this)';
private static $_request_id;
private static $_request_time;
* All instances of this class.
* @var ET_Core_PageResource[] {
* @type ET_Core_PageResource $slug
private static $_resources;
* All instances of this class organized by output location and sorted by priority.
* @type array[] $location {@see self::$_OUTPUT_LOCATIONS} {
* @type ET_Core_PageResource[] $priority {
* @type ET_Core_PageResource $slug
private static $_resources_by_location;
* All instances of this class organized by scope.
* @type ET_Core_PageResource[] $post|$global {
* @type ET_Core_PageResource $slug
private static $_resources_by_scope;
public static $WP_CONTENT_DIR;
public static $current_output_location;
* @var ET_Core_Data_Utils
public static $data_utils;
* @var \WP_Filesystem_Base|null
* The absolute path to the directory where the static resource will be stored.
* The absolute path to the static resource on the server.
* The absolute URL through which the static resource can be downloaded.
* The data/contents for/of the static resource sorted by priority.
* Whether or not this resource has been disabled.
* Whether or not the static resource file has been enqueued.
* Whether or not this resource is forced inline.
* Whether or not the resource has already been output to the page inline.
* The owner of this instance.
* The id of the post to which this resource belongs.
* The priority of this resource.
* A unique identifier for this resource.
* The resource type (style|script).
* The output location during which this resource's static file should be generated.
public $write_file_location;
* ET_Core_PageResource constructor
* @param string $owner The owner of the instance (core|divi|builder|bloom|monarch|custom).
* @param string $slug A string that uniquely identifies the resource.
* @param string|int $post_id The post id that the resource is associated with or `global`.
* If `null`, {@link get_the_ID()} will be used.
* @param string $type The resource type (style|script). Default: `style`.
* @param string $location Where the resource should be output (head|footer). Default: `head`.
public function __construct( $owner, $slug, $post_id = null, $priority = 10, $location = 'head-late', $type = 'style' ) {
$this->owner = self::_validate_property( 'owner', $owner );
$this->post_id = self::_validate_property( 'post_id', $post_id ? $post_id : et_core_page_resource_get_the_ID() );
$this->type = self::_validate_property( 'type', $type );
$this->location = self::_validate_property( 'location', $location );
$this->write_file_location = $this->location;
$this->filename = sanitize_file_name( "et-{$this->owner}-{$slug}-{$post_id}" );
$this->slug = "{$this->filename}-cached-inline-{$this->type}s";
$this->priority = $priority;
$this->_initialize_resource();
public static function startup() {
if ( null !== self::$_resources ) {
// Class has already been initialized
$time = (string) microtime( true );
$time = str_replace( '.', '', $time );
$rand = (string) mt_rand();
self::$_request_time = $time;
self::$_request_id = "{$time}-{$rand}";
self::$_resources = array();
self::$data_utils = new ET_Core_Data_Utils();
foreach ( self::$_OUTPUT_LOCATIONS as $location ) {
self::$_resources_by_location[ $location ] = array();
foreach( self::$_SCOPES as $scope ) {
self::$_resources_by_scope[ $scope ] = array();
self::$WP_CONTENT_DIR = self::$data_utils->normalize_path( WP_CONTENT_DIR );
self::$_LOCK_FILE = self::$_request_id . '~';
self::_register_callbacks();
self::_setup_wp_filesystem();
self::$_can_write = et_core_cache_dir()->can_write;
* Updates our resource array in the database if needed.
public static function shutdown() {
if ( ! self::$_resources || ! self::$_can_write ) {
// Remove any leftover temporary directories that belong to this request
foreach ( self::$_TEMP_DIRS as $temp_directory ) {
if ( file_exists( $temp_directory . '/' . self::$_LOCK_FILE ) ) {
@self::$wpfs->delete( $temp_directory, true );
// Reset $_resources property; Mostly useful for unit test big request which needs to make
// each test*() method act like it is different page request
self::$_resources = null;
if ( et_()->WPFS()->exists( self::$WP_CONTENT_DIR . '/cache/et' ) ) {
// Remove old cache directory
et_()->WPFS()->rmdir( self::$WP_CONTENT_DIR . '/cache/et', true );
protected static function _assign_output_location( $location, $resource ) {
$priority_existed = isset( self::$_resources_by_location[ $location ][ $resource->priority ] );
self::$_resources_by_location[ $location ][ $resource->priority ][ $resource->slug ] = $resource;
if ( ! $priority_existed ) {
// We've added a new priority to the list, so put them back in sorted order.
ksort( self::$_resources_by_location[ $location ], SORT_NUMERIC );
* Enqueues static file for provided script resource.
* @param ET_Core_PageResource $resource
protected static function _enqueue_script( $resource ) {
$can_enqueue = 0 === did_action( 'wp_print_scripts' );
wp_enqueue_script( $resource->slug, set_url_scheme( $resource->URL ), array(), ET_CORE_VERSION, true );
'<script id="%1$s" src="%2$s"></script>', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
esc_attr( $resource->slug ),
esc_url( set_url_scheme( $resource->URL ) )
$resource->enqueued = true;
* Enqueues static file for provided style resource.
* @param ET_Core_PageResource $resource
protected static function _enqueue_style( $resource ) {
if ( 'footer' === self::$current_output_location ) {
$can_enqueue = 0 === did_action( 'wp_print_styles' );
wp_enqueue_style( $resource->slug, set_url_scheme( $resource->URL ) );
'<link rel="stylesheet" id="%1$s" href="%2$s" onerror="%3$s" onload="%4$s" />', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
esc_attr( $resource->slug ),
esc_url( set_url_scheme( $resource->URL ) ),
et_core_esc_previously( self::$_onerror ),
et_core_esc_previously( self::$_onload )
$resource->enqueued = true;
* Returns the next output location.
* @see self::$_OUTPUT_LOCATIONS
protected static function _get_next_output_location() {
$current_index = array_search( self::$current_output_location, self::$_OUTPUT_LOCATIONS );
if ( false === $current_index || ! is_int( $current_index ) ) {
ET_Core_Logger::error( '$current_output_location is invalid!' );
return self::$_OUTPUT_LOCATIONS[ $current_index ];
* Creates static resource files for an output location if needed.
* @param string $location {@link self::$_OUTPUT_LOCATIONS}
protected static function _maybe_create_static_resources( $location ) {
self::$current_output_location = $location;
$sorted_resources = self::get_resources_by_output_location( $location );
foreach ( $sorted_resources as $priority => $resources ) {
foreach ( $resources as $slug => $resource ) {
if ( $resource->write_file_location !== $location ) {
// This resource's static file needs to be generated later on.
self::_assign_output_location( $resource->write_file_location, $resource );
if ( ! self::$_can_write ) {
// The reason we don't simply check this before looping through resources and
// bail if it fails is because we need to perform the output location assignment
// in the previous conditional regardless (otherwise builder styles will break).
if ( $resource->forced_inline || $resource->has_file() ) {
$data = $resource->get_data( 'file' );
if ( empty( $data ) && 'footer' !== $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 );
// Make sure directory exists.
if ( ! self::$data_utils->ensure_directory_exists( $resource->BASE_DIR ) ) {
self::$_can_write = false;
// Try to create a temporary directory which we'll use as a pseudo file lock
if ( @mkdir( $resource->TEMP_DIR, 0755 ) ) {
self::$_TEMP_DIRS[] = $resource->TEMP_DIR;
// Make sure another request doesn't delete our temp directory
$lock_file = $resource->TEMP_DIR . '/' . self::$_LOCK_FILE;
self::$wpfs->put_contents( $lock_file, '' );
// Create the static resource file
if ( ! self::$wpfs->put_contents( $resource->PATH, $data, 0644 ) ) {
// There's no point in continuing
self::$_can_write = $can_continue = false;
// Remove the temporary directory
self::$wpfs->delete( $resource->TEMP_DIR, true );
} else if ( file_exists( $resource->TEMP_DIR ) ) {
// The static resource file is currently being created by another request