| Current Path : /home/ereika83/www/wp-content/plugins/wp-hummingbird/core/modules/ |
| Current File : /home/ereika83/www/wp-content/plugins/wp-hummingbird/core/modules/class-critical-css.php |
<?php
/**
* Critical CSS module.
*
* @package Hummingbird\Core\Modules
* @since 3.6.0
*/
namespace Hummingbird\Core\Modules;
use Hummingbird\Core\Filesystem;
use Hummingbird\Core\Module;
use Hummingbird\Core\Modules\Minify\Fonts;
use Hummingbird\Core\Modules\Caching\Fast_CGI;
use Hummingbird\Core\Traits\Module as ModuleContract;
use Hummingbird\Core\Utils;
use Hummingbird\Core\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Critical CSS
*/
class Critical_Css extends Module {
use ModuleContract;
/**
* Key to store critical status for a single post.
*
* @var string
*/
const CRITICAL_POST_META_KEY = '_wphb_singular_css_status';
/**
* Key to store critical css process queue.
*
* @var string
*/
const QUEUE_OPTION_ID = 'wphb_cs_process_queue';
/**
* Transient expiration timeout.
*
* @var string
*/
const TRANSIENT_EXPIRATION = 30;
/**
* Transient name.
*
* @var string
*/
const TRANSIENT_NAME = 'wphb-cs-processing';
/**
* Store all the items.
*
* @var array
*/
private $items = array();
/**
* Fonts class.
*
* @var Font\Fonts
*/
private $fonts;
/**
* Holds excluded stylesheet IDs.
*
* @var array
*/
public $excluded_stylesheet_ids = array();
/**
* Holds all combined exclusions.
*
* @var array
*/
public $combined_exclusions = null;
/**
* Initialize module.
*
* @since 3.6.0
*/
public function init() {
$this->fonts = new Fonts();
add_filter( 'wp_hummingbird_is_active_module_critical_css', array( $this, 'module_status' ) );
add_filter( 'wp_hummingbird_default_options', array( $this, 'wp_hummingbird_default_options' ) );
}
/**
* Execute module actions.
*
* @since 3.6.0
*/
public function run() {
add_filter( 'wphb_dont_combine_handles', array( $this, 'wphb_dont_combine_handles' ), 10, 3 );
add_filter( 'wphb_should_cache_exit', array( $this, 'should_cache_exit' ) );
add_filter( 'wphb_buffer', array( $this, 'add_critical_css' ) );
// Process the cs queue through WP Cron.
add_action( 'wphb_cs_process_queue_cron', array( $this, 'generate_critical_for_queue' ) );
add_action( 'wphb_cs_ping_queue_cron', array( $this, 'get_critical_for_queue' ) );
add_action( 'admin_init', array( $this, 'schedule_get_critical_cron' ), 20000 );
add_action( 'wp_footer', array( $this, 'schedule_cron' ), 20000 );
add_action( 'wp_head', array( $this, 'insert_load_css_script' ) );
add_action( 'after_switch_theme', array( $this, 'regenerate_critical_css' ) );
add_filter( 'wphb_minify_resource', array( $this, 'filter_resource_minify' ), 10, 4 );
}
/**
* Add page types to minify settings.
*
* @param array $defaults An array of default settings.
*
* @return array
*/
public function wp_hummingbird_default_options( $defaults ) {
$defaults['minify']['critical_page_types'] = Page_Cache::get_page_types( true );
return $defaults;
}
/**
* Get module status.
*
* @return bool
*/
public function module_status() {
if ( ! Utils::is_member() ) {
return false;
}
$options = Settings::get_settings( 'minify' );
$critical_css = $options['critical_css'];
$critical_css_mode = $options['critical_css_mode'];
if ( ! $options['enabled'] || ! $critical_css || ( 'critical_css' !== $critical_css_mode ) ) {
return false;
}
return true;
}
/**
* Function to ignore minify, if critical css is enabled.
*
* @param boolean $result Result.
* @param string $handles Asset Handles.
* @param string $type Asset type.
*
* @return bool
*/
public function wphb_dont_combine_handles( $result, $handles, $type ) {
$options = Utils::get_module( 'minify' )->get_options();
$critical_css_type = $options['critical_css_type'];
if ( 'styles' === $type && 'remove' === $critical_css_type ) {
return true;
}
return $result;
}
/**
* Add into Item.
*
* @param string $url URL.
* @param string $type Type.
* @param string $singular Is singular.
*/
private function add_item( $url, $type, $singular = '' ) {
// Nothing to be added.
if ( empty( $url ) || empty( $type ) || ! empty( $_GET ) ) {
return;
}
$url = add_query_arg(
array(
'avoid-minify' => true,
'hb_doing_critical' => 1,
),
$url
);
$queue = array(
'url' => $url,
'type' => $type,
'singular' => $singular, // For the individual pages.
'status' => 'pending', // pending - we have not sent the data for processing, processing - we have sent the data for processing, complete - response has been received.
'result' => false, // False for no css generated , True for css generated .
'error_message' => '',
'error_code' => '',
'last_updated' => '',
'display_notice' => 0, // 0 - Notice not displayed yet, 1 - Notice has been displayed.
'id' => '',
'hash' => $this->hash( $type ),
);
$this->items[ $queue['hash'] ] = (object) $queue;
}
/**
* Sets the items for critical CSS generation.
*/
private function set_items() {
// Add frontpage page url to queue.
if ( ! $this->skip_page_type( 'frontpage' ) ) {
$this->add_item( home_url( '/' ), 'frontpage' );
}
$page_for_posts = get_option( 'page_for_posts' );
if ( 'page' === get_option( 'show_on_front' ) && ! empty( $page_for_posts ) ) {
if ( ! $this->skip_page_type( 'home' ) ) {
$this->add_item( get_permalink( get_option( 'page_for_posts' ) ), 'home' );
}
}
$post_types = $this->get_public_post_types();
$site_url_length = strlen( get_site_url() );
foreach ( $post_types as $post_type ) {
$get_post_type_url = get_permalink( $post_type->ID );
if ( ! empty( $get_post_type_url ) && substr( $get_post_type_url, 0, $site_url_length ) === get_site_url() ) {
if ( ! $this->skip_page_type( $post_type->post_type ) ) {
$this->add_item( $get_post_type_url, $post_type->post_type );
}
}
}
$taxonomies = $this->get_public_taxonomies();
foreach ( $taxonomies as $taxonomy ) {
$get_term_url = get_term_link( (int) $taxonomy->ID );
if ( ! empty( $get_term_url ) && substr( $get_term_url, 0, $site_url_length ) === get_site_url() ) {
if ( ! $this->skip_page_type( $taxonomy->taxonomy ) ) {
$this->add_item( $get_term_url, $taxonomy->taxonomy );
}
}
}
$this->items = (array) apply_filters( 'wphb_css_items', $this->items );
}
/**
* Push items into queue.
*
* @param bool $overwrite Overwrite.
*
* @return boolean
*/
private function persist_queue_to_db( $overwrite = false ) {
if ( empty( $this->items ) ) {
return false;
}
$current_queue = $this->get_persistent_queue();
if ( empty( $current_queue ) ) {
update_option( self::QUEUE_OPTION_ID, $this->items );
return true;
}
$updated = false;
foreach ( $this->items as $hash => $item ) {
if ( empty( $current_queue[ $hash ] ) || $overwrite ) {
$updated = true;
$current_queue[ $hash ] = $item;
}
}
if ( $updated ) {
update_option( self::QUEUE_OPTION_ID, $current_queue );
}
return $updated;
}
/**
* Toggle critical CSS function.
*
* @param bool $value Critical CSS to set.
*/
public function toggle_critical_css( $value ) {
$minify_options = Utils::get_module( 'minify' );
$options = $minify_options->get_options();
$options['critical_css'] = $value;
$options['critical_css_mode'] = 'critical_css';
$minify_options->update_options( $options );
// Clear queue and generate critical css.
if ( ! empty( $value ) ) {
$this->regenerate_critical_css();
}
}
/**
* General purpose function. Returns an array hashed.
*
* @param array|string $list Array of strings or single string.
*
* @return string
*/
private function hash( $list ) {
return wp_hash( maybe_serialize( $list ) );
}
/**
* Should Cache Exit.
*
* @param bool $should_exit Should Cache Exit. Default: false.
*/
public function should_cache_exit( $should_exit ) {
$queue = $this->get_persistent_queue( array( 'pending', 'processing' ) ); // Fetch only Pending queue.
if ( empty( $queue ) ) {
return $should_exit;
}
$type = $this->get_url_type();
if ( empty( $type ) ) {
return $should_exit;
}
$all_pages = array();
foreach ( $queue as $item ) {
$all_pages[] = $item->type;
}
$singular_css_id = $this->get_singular_page_id();
$single_post_critical_css_status = $this->get_single_post_critical_css_status( $singular_css_id );
if ( in_array( $type, $all_pages, true ) || 'processing' === $single_post_critical_css_status ) {
return true;
}
return $should_exit;
}
/**
* Determines if we can add critical css.
*
* @return boolean
*/
public function should_add_critical_css() {
if ( isset( $_GET['hb_doing_critical'] ) && 1 === absint( $_GET['hb_doing_critical'] ) ) {
return false;
}
if ( is_user_logged_in() ) {
return false;
}
if ( ! apply_filters( 'wphb_should_add_critical_css', true ) ) {
return false;
}
if ( ( defined( 'WPHBDONOTOPTIMIZE' ) && WPHBDONOTOPTIMIZE ) || Utils::is_amp() || Utils::wphb_is_page_builder() || is_preview() || is_customize_preview() ) {
return false;
}
if ( Utils::get_module( 'exclusions' )->is_current_page_excluded( 'critical_css' ) ) {
return false;
}
return true;
}
/**
* Function to apply critical css in buffer.
*
* @param string $html HTML page buffer.
*
* @return string
*/
public function add_critical_css( $html ) {
if ( ! $this->should_add_critical_css() ) {
return $html;
}
// Only known url types.
$type = $this->get_url_type();
if ( empty( $type ) || $this->ignore_types( $type ) || $this->skip_page_type( $type ) ) {
return $html;
}
// Check if singular type exists.
$singular_type = $this->maybe_get_singular_page_type();
if ( false === $singular_type ) {
return $html;
}
$type = $singular_type ?: $type;
// Setup file variables.
$used_css_path = $this->used_css_path( $this->maybe_get_critical_css_path_for_mobile( $type ) );
$used_css_exists = file_exists( $used_css_path );
// If Used CSS File exists.
if ( $used_css_exists ) {
$options = Utils::get_module( 'minify' )->get_options();
// Delay stylesheets.
if ( empty( $options['critical_css_type'] ) || 'remove' === $options['critical_css_type'] ) {
if ( 'user_interaction_with_remove' === $options['critical_css_remove_type'] ) {
$html = $this->load_stylesheet_on_user_interaction( $html );
} else {
$html = $this->remove_used_css_from_html( $html );
}
} elseif ( 'asynchronously' === $options['critical_css_type'] ) {
if ( 'load_stylesheet_on_user_interaction' === $options['above_fold_load_stylesheet_method'] ) {
$html = $this->load_stylesheet_on_user_interaction( $html );
} else {
$html = $this->make_css_async( $html );
}
}
// Print used css inline after first title tag.
$pos = strpos( $html, '</title>' );
if ( false !== $pos ) {
// IF critical css is generated.
$generated_critical = apply_filters( 'wphb_generated_used_css', file_get_contents( $used_css_path ) );
$used_css_output = $this->get_used_css_markup( $this->maybe_get_critical_css_path_for_mobile( $type ), $generated_critical );
$html = substr_replace( $html, '</title>' . $used_css_output, $pos, 8 );
$html = $this->fonts->add_preload_to_fonts_in_used_css( $html, $generated_critical );
}
}
// If generated critical css is not available, generate.
if ( ! $used_css_exists ) {
$req_url = is_single() ? get_permalink() : home_url( $_SERVER['REQUEST_URI'] );
$singular = '';
// For singular pages.
if ( 'frontpage' === $type || strpos( $type, 'page-' ) !== false ) {
$singular = get_the_ID();
}
$this->add_item( $req_url, $type, $singular );
// If new Item is pushed.
if ( $this->persist_queue_to_db() ) {
// Fire the cron.
$this->schedule_cron();
}
}
return $html;
}
/**
* Get all stylesheets in the HTML.
*
* @param string $html HTML code.
*
* @return array
*/
public function get_stylesheets( $html ) {
/**
* Filters the pattern used to get all stylesheets in the HTML.
*
* @since 3.6.0
*/
$stylesheet_pattern = apply_filters(
'wphb_css_stylesheet_pattern',
'/(?=<link[^>]*\s(rel\s*=\s*[\'"]stylesheet[\'"]))(?:<link[^>]*\shref\s*=\s*[\'"]([^\'"]+)[\'"](.*)>)/iU'
);
preg_match_all( $stylesheet_pattern, $html, $stylesheets, PREG_SET_ORDER );
return $stylesheets;
}
/**
* Load stylesheet on user interaction.
*
* @param string $html HTML code.
*
* @return string
*/
public function load_stylesheet_on_user_interaction( $html ) {
$stylesheets = $this->get_stylesheets( $html );
if ( ! empty( $stylesheets ) ) {
foreach ( $stylesheets as $stylesheet ) {
if ( ! apply_filters( 'wphb_should_load_stylesheet_on_user_interaction', true, $stylesheet ) ) {
continue;
}
$stylesheet_attr = $stylesheet[0];
if ( $this->is_stylesheet_excluded( $stylesheet_attr ) ) {
continue;
}
$style_href = trim( $stylesheet[2] );
$new_link = preg_replace( '#href=([\'"]).+?\1#', 'data-wphbdelayedstyle="' . $style_href . '"', $stylesheet_attr );
$html = str_replace( $stylesheet_attr, $new_link, $html );
}
$script = '<script type="text/javascript" id="wphb-delayed-styles-js">
(function () {
const events = ["keydown", "mousemove", "wheel", "touchmove", "touchstart", "touchend"];
function wphb_load_delayed_stylesheets() {
document.querySelectorAll("link[data-wphbdelayedstyle]").forEach(function (element) {
element.setAttribute("href", element.getAttribute("data-wphbdelayedstyle"));
}),
events.forEach(function (event) {
window.removeEventListener(event, wphb_load_delayed_stylesheets, { passive: true });
});
}
events.forEach(function (event) {
window.addEventListener(event, wphb_load_delayed_stylesheets, { passive: true });
});
})();
</script>';
$html = str_replace( '</body>', $script . '</body>', $html );
}
return $html;
}
/**
* Check if the stylesheet is excluded from critical css.
*
* @param string $stylesheet_attr Stylesheet attribute.
*
* @return array
*/
public function is_stylesheet_excluded( $stylesheet_attr ) {
$combined_exclusions = $this->get_combined_exclusions();
return array_reduce(
$combined_exclusions,
fn( $carry, $item ) => $carry || stripos( $stylesheet_attr, $item ) !== false,
false
);
}
/**
* Get all combined exclusions.
*
* @return array
*/
private function get_combined_exclusions() {
if ( is_null( $this->combined_exclusions ) ) {
$this->combined_exclusions = array_merge(
Utils::get_module( 'exclusions' )->get_combined_asset_path_exclusion_list_for_critical_css(),
$this->excluded_stylesheet_ids
);
}
return $this->combined_exclusions;
}
/**
* Remove all CSS which was used on the current page.
*
* @param string $html HTML content.
*
* @return string
*/
public function remove_used_css_from_html( $html ) {
$stylesheets = $this->get_stylesheets( $html );
if ( ! empty( $stylesheets ) ) {
foreach ( $stylesheets as $stylesheet ) {
$stylesheet_attr = $stylesheet[0];
if ( $this->is_stylesheet_excluded( $stylesheet_attr ) ) {
continue;
}
$html = str_replace( $stylesheet_attr, '', $html );
}
}
return $html;
}
/**
* Convert CSS stylesheet to load asynchronously.
*
* @param string $html HTML code.
*
* @return string
*/
public function make_css_async( $html ) {
$stylesheets = $this->get_stylesheets( $html );
if ( ! empty( $stylesheets ) ) {
$noscripts = '<noscript>';
foreach ( $stylesheets as $stylesheet ) {
// Skip stylesheets with media="print".
if ( stripos( $stylesheet[0], 'media="print"' ) !== false || stripos( $stylesheet[0], "media='print'" ) !== false ) {
continue;
}
// Prepare the replacements.
$preload = str_replace( 'stylesheet', 'preload', $stylesheet[1] );
$onload = str_replace( $stylesheet[3] . '>', ' as="style" onload="" ' . $stylesheet[3] . '>', $stylesheet[0] );
$onload = str_replace( 'onload=""', 'onload="this.onload=null;this.rel=\'stylesheet\'"', $onload );
$stylesheet_tag = str_replace( $stylesheet[1], $preload, $onload );
$stylesheet_tag = preg_replace( '/\s*id\s*=\s*[\'"][^\'"]*[\'"]/', '', $stylesheet_tag );
// Replace the original stylesheet tag in the HTML.
$html = str_replace( $stylesheet[0], $stylesheet_tag, $html );
$noscripts .= $stylesheet[0];
}
$noscripts .= '</noscript>';
$html = str_replace( '</body>', $noscripts . '</body>', $html );
}
return $html;
}
/**
* Return Markup for used_css into the page.
*
* @param string $type Current page type.
* @param string $used_css_contents Used CSS content.
*
* @return string
*/
public function get_used_css_markup( $type, $used_css_contents ) {
$manual_critical = Minify::get_css( 'manual-critical' );
return sprintf( /* translators: %1$s - page type, %2$s - Used css content, %3$s - manual critical css */
'<style id="wphb-used-css-%1$s">%2$s%3$s</style>',
$type,
$used_css_contents,
$manual_critical
);
}
/**
* Get path to store used css.
*
* @return string
*/
public function get_critical_css_path() {
if ( is_multisite() ) {
$blog = get_blog_details();
if ( '/' === $blog->path ) {
$site = trailingslashit( $blog->domain );
} else {
$site = $blog->path;
}
} else {
$http_host = get_option( 'siteurl' );
if ( ! empty( $http_host ) ) {
$http_host = preg_replace( '/^https?:\/\/|\/$/', '', $http_host );
} elseif ( ! empty( $_SERVER['HTTP_HOST'] ) ) {
$http_host = htmlentities( wp_unslash( $_SERVER['HTTP_HOST'] ) ); // Input var ok.
}
$site = $http_host . '/';
}
// Remove starting www.
if ( strpos( $site, 'www.' ) !== false ) {
$used_css_dir = WP_CONTENT_DIR . '/wphb-cache/critical-css/' . $site;
$is_used_css_dir = is_dir( $used_css_dir );
// If Used CSS directory exists.
if ( ! $is_used_css_dir ) {
$site = preg_replace( '/^(www\.)/', '', $site );
}
}
return WP_CONTENT_DIR . '/wphb-cache/critical-css/' . $site;
}
/**
* Set url to store used css
*
* @return string
*/
public function get_critical_css_url() {
$site = '';
if ( is_multisite() ) {
$blog = get_blog_details();
if ( '/' === $blog->path ) {
$site = trailingslashit( $blog->domain );
} else {
$site = $blog->path;
}
}
return trailingslashit( content_url() ) . '/wphb-cache/critical-css/' . $site;
}
/**
* Get path to store used css.
*
* @param string $type Script or style.
*
* @return string
*/
public function used_css_path( $type ) {
return $this->get_critical_css_path() . '/' . $type . '-used.css';
}
/**
* Check if critical css for singular post is exist.
*
* @return string|boolean
*/
private function maybe_get_singular_page_type() {
// Check if singular type exists.
$singular_css_id = $this->get_singular_page_id();
$single_post_critical_css_status = $this->get_single_post_critical_css_status( $singular_css_id );
if ( $single_post_critical_css_status ) {
// If there was an error in generating singular file or File is being generated.
if ( 'error' === $single_post_critical_css_status || 'processing' === $single_post_critical_css_status ) {
return false;
}
$singular_type = $this->get_singular_page_type();
$singular_used_css_path = $this->used_css_path( $singular_type );
$singular_used_css_exists = file_exists( $singular_used_css_path );
// Update the type.
if ( $singular_used_css_exists ) {
return $singular_type;
}
}
return '';
}
/**
* Get the url type.
*
* @return string
*/
public function get_url_type() {
global $wp_query;
$type = '';
if ( $wp_query->is_page || is_front_page() ) {
$type = is_front_page() ? 'frontpage' : 'page-' . $wp_query->post->ID;
} elseif ( $wp_query->is_home ) {
$type = 'home';
} elseif ( $wp_query->is_single ) {
$type = get_post_type() !== false ? get_post_type() : 'single';
} elseif ( $wp_query->is_category ) {
$type = 'category';
} elseif ( $wp_query->is_tag ) {
$type = 'post_tag';
} elseif ( $wp_query->is_tax ) {
$term = get_queried_object();
$type = $term->taxonomy;
} elseif ( $wp_query->is_archive ) {
$type = $wp_query->is_day ? 'day' : ( $wp_query->is_month ? 'month' : ( $wp_query->is_year ? 'year' : ( $wp_query->is_author ? 'author' : 'archive' ) ) );
}
return $type;
}
/**
* Returns the correct type so that we can correctly check whether to exclude it or not.
*
* @param string $type type.
*
* @return string
*/
public function get_mapped_type_name_for_skipping( $type ) {
if ( 'post' === $type ) {
return 'single';
} elseif ( 'post_tag' === $type ) {
return 'tag';
} elseif ( strpos( $type, 'page-' ) !== false ) {
return 'page';
} elseif ( in_array( $type, array( 'day', 'month', 'year', 'author' ), true ) ) {
return 'archive';
}
return $type;
}
/**
* Process the queue.
*
* @return boolean|void
*/
public function generate_critical_for_queue() {
// Process the queue.
if ( get_transient( self::TRANSIENT_NAME ) ) {
// Still processing. Try again.
if ( ! $this->is_cron_disabled() ) {
$this->maybe_schedule_generate_critical_cron();
}
return;
}
$queue = $this->get_persistent_queue( array( 'pending' ) ); // Get only pending queue to proceed.
if ( empty( $queue ) ) {
return;
}
set_transient( self::TRANSIENT_NAME, true, self::TRANSIENT_EXPIRATION );
$status = $this->send_generate_critical_api_request();
delete_transient( self::TRANSIENT_NAME );
return $status;
}
/**
* Ping the critical api.
*/
public function get_critical_for_queue() {
// Process the queue.
if ( get_transient( self::TRANSIENT_NAME ) ) {
// Still processing. Try again.
if ( ! $this->is_cron_disabled() ) {
$this->maybe_schedule_get_critical_cron();
}
return;
}
$queue = $this->get_persistent_queue( array( 'processing' ) ); // Get only processed queue.
if ( empty( $queue ) ) {
return;
}
set_transient( self::TRANSIENT_NAME, true, self::TRANSIENT_EXPIRATION );
$this->fetch_generated_css_from_api();
$updated_queue = $this->get_persistent_queue( array( 'processing' ) ); // Get only processed queue.
if ( ! $this->is_cron_disabled() ) {
if ( ! empty( $updated_queue ) ) {
// Still needs processing.
$this->maybe_schedule_get_critical_cron();
}
}
if ( empty( $updated_queue ) ) {
// Finish processing.
delete_transient( self::TRANSIENT_NAME );
}
}
/**
* Schedule queue process through WP Cron.
*/
public function maybe_schedule_generate_critical_cron() {
if ( ! wp_next_scheduled( 'wphb_cs_process_queue_cron' ) ) {
wp_schedule_single_event( time(), 'wphb_cs_process_queue_cron' );
}
}
/**
* Schedule queue process through WP Cron.
*/
public function maybe_schedule_get_critical_cron() {
if ( ! wp_next_scheduled( 'wphb_cs_ping_queue_cron' ) ) {
wp_schedule_single_event( time(), 'wphb_cs_ping_queue_cron' );
}
}
/**
* Update queue item with respective statuses.
*
* @param string $hash Item hash.
* @param bool $status Process status.
* @param int $id Queue ID.
* @param string $result Result.
* @param string $error_message Error message.
* @param string $error_code Error code.
*/
private function update_item_in_persistent_queue( $hash, $status = false, $id = '', $result = '', $error_message = '', $error_code = '' ) {
$queue = $this->get_persistent_queue();
if ( ! empty( $queue[ $hash ] ) ) {
if ( false !== $status ) {
$queue[ $hash ]->status = $status;
}
if ( $id ) {
$queue[ $hash ]->id = $id;
}
$get_date_time = date_i18n( get_option( 'date_format' ) ) . ' @ ' . date_i18n( get_option( 'time_format' ) );
if ( '' !== $result ) {
$queue[ $hash ]->result = $result;
$queue[ $hash ]->error_message = $error_message;
$queue[ $hash ]->error_code = $error_code;
$queue[ $hash ]->last_updated = $get_date_time;
}
$log_data = array(
'status' => $status,
'result' => $result,
'error_message' => $error_message,
'error_code' => $error_code,
'last_updated' => $get_date_time,
);
update_option( 'wphb_critical_css_log', $log_data );
update_option( self::QUEUE_OPTION_ID, $queue );
return true;
}
return false;
}
/**
* Get the list of groups that are yet pending to be processed.
*
* @param array $status An array of status.
*/
public function get_persistent_queue( $status = array() ) {
$wphb_cs_process_queue = $this->get_queue_option();
if ( empty( $status ) || empty( $wphb_cs_process_queue ) ) {
return $wphb_cs_process_queue;
}
$process_array = array();
foreach ( $wphb_cs_process_queue as $hash => $item ) {
if ( isset( $item->status ) && in_array( $item->status, $status, true ) ) {
$process_array[ $hash ] = $item;
}
}
return $process_array;
}
/**
* Get the critical queue by hash.
*
* @param bool $hash Hash.
*/
public function get_queue_item_by_hash( $hash ) {
$wphb_cs_process_queue = $this->get_queue_option();
if ( false === $hash || empty( $wphb_cs_process_queue ) || empty( $wphb_cs_process_queue[ $hash ] ) ) {
return false;
}
return $wphb_cs_process_queue[ $hash ];
}
/**
* Fetch the first id.
*/
public function fetch_id_from_processing_queue() {
$wphb_cs_process_queue = $this->get_queue_option();
if ( empty( $wphb_cs_process_queue ) ) {
return false;
}
$id = false;
foreach ( $wphb_cs_process_queue as $hash => $item ) {
if ( isset( $item->status ) && 'processing' === $item->status ) {
$id = $item->id;
break;
}
}
return $id;
}
/**
* Fetch all the queue by id.
*
* @param int $id ID.
*/
public function get_queue_items_by_job_id( $id ) {
$process_array = array();
$wphb_cs_process_queue = $this->get_queue_option();
if ( empty( $wphb_cs_process_queue ) && false === $id ) {
return $process_array;
}
foreach ( $wphb_cs_process_queue as $hash => $item ) {
if ( isset( $item->id ) && $item->id === $id ) {
$process_array[ $hash ] = $item;
}
}
return $process_array;
}
/**
* Deletes the persistent queue completely
*/
public function delete_pending_persistent_queue() {
delete_option( self::QUEUE_OPTION_ID );
wp_cache_delete( self::QUEUE_OPTION_ID, 'options' );
}
/**
* Clear pending queue.
*/
public function clear_pending_process_queue() {
$this->delete_pending_persistent_queue();
delete_transient( self::TRANSIENT_NAME );
// Clear cron events.
if ( wp_next_scheduled( 'wphb_cs_process_queue_cron' ) ) {
wp_clear_scheduled_hook( 'wphb_cs_process_queue_cron' );
}
// Clear cron events.
if ( wp_next_scheduled( 'wphb_cs_ping_queue_cron' ) ) {
wp_clear_scheduled_hook( 'wphb_cs_ping_queue_cron' );
}
}
/**
* CLear critical css data and files.
*/
public function regenerate_critical_css() {
if ( ! $this->is_active() ) {
return;
}
// Clear cache before generating the critical css.
Utils::get_module( 'page_cache' )->clear_cache();
$this->clear_pending_process_queue();
$this->delete_all_post_meta_for_critical();
$this->delete_css_cache_files();
$this->set_items();
$this->persist_queue_to_db();
$this->generate_critical_for_queue();
}
/**
* Trigger the action to process the ping queue.
*/
public function schedule_get_critical_cron() {
$queue_processing = $this->get_persistent_queue( array( 'processing' ) ); // Get all the pending Queue.
if ( ! empty( $queue_processing ) ) {
if ( $this->is_cron_disabled() ) {
$this->get_critical_for_queue();
} else {
$this->maybe_schedule_get_critical_cron();
}
}
}
/**
* Schedule the first cron if we need to process the queue for generation or schedule the second cron if we need to fetch the generated critical css.
*/
public function schedule_cron() {
$this->schedule_generate_critical_cron();
$this->schedule_get_critical_cron();
}
/**
* Process the critical css.
*
* @return boolean|void
*/
public function send_generate_critical_api_request() {
$queue = $this->get_persistent_queue( array( 'pending' ) ); // Get only pending queue to proceed.
if ( empty( $queue ) ) {
return;
}
$urls = null;
$api = Utils::get_api();
$options = Utils::get_module( 'minify' )->get_options();
if ( empty( $options['critical_css_type'] ) || 'remove' === $options['critical_css_type'] ) {
$api_call_type = 'PURGE';
} else {
$api_call_type = 'CRITICAL';
}
foreach ( $queue as $hash => $item ) {
$urls[ $hash ] = $this->get_item_data_for_critical_calculate_api( $item );
if ( $this->is_mobile_critical_css_allowed() ) {
$urls[ $hash . '-mobile' ] = $this->get_item_data_for_critical_calculate_api( $item, true );
}
}
$response = $api->critical_css->generate_critical_css( $urls, $api_call_type );
$is_type_error = true;
if ( ! is_wp_error( $response ) && ! empty( $response ) ) {
$response = json_decode( wp_remote_retrieve_body( (array) $response ) );
// Fetch the id.
$success = ! empty( $response ) && 1 === absint( $response->success );
if ( $success ) {
// Fetch the id.
$response_id = ! empty( $response->id ) ? $response->id : false;
Utils::get_module( 'minify' )->log( 'Response ID from the calculate critical endpoint - ' . $response_id );
// Update Queue.
$is_type_error = false;
// If id is received from the response, update in the queue.
if ( $response_id ) {
foreach ( $urls as $hash => $url ) {
$this->update_item_in_persistent_queue( $hash, 'processing', $response_id );
}
}
}
}
// Log the error.
if ( $is_type_error ) {
$api_error = is_wp_error( $response ) ? $response->get_error_message() : esc_html__( 'HB critical unknown error', 'wphb' );
Utils::get_module( 'minify' )->log( 'There is an error in calling critical api ' . $api_error );
// Update the pending queues.
foreach ( $urls as $hash => $url ) {
$this->update_item_in_persistent_queue( $hash, 'complete', '', 'ERROR', $api_error );
$hash_object = $this->get_queue_item_by_hash( $hash );
$singular = ! empty( $hash_object->singular ) ? $hash_object->singular : '';
if ( ! empty( $singular ) ) {
$this->delete_post_meta_for_single_post( $singular );
update_post_meta( $singular, self::CRITICAL_POST_META_KEY, 'error' );
}
}
}
return ! $is_type_error;
}
/**
* Ping the critical api to fetch the css.
*
* @return boolean|void
*/
public function fetch_generated_css_from_api() {
$fs = Filesystem::instance();
$id = $this->fetch_id_from_processing_queue();
if ( ! $id ) {
return;
}
$api = Utils::get_api();
$response = $api->critical_css->get_generated_critical_css( $id );
if ( ! is_wp_error( $response ) ) {
$response = json_decode( wp_remote_retrieve_body( (array) $response ) );
$status = isset( $response->status ) ? $response->status : '';
$urls = isset( $response->items ) ? $response->items : '';
$error_message = ! empty( $response->errorMessage ) ? $response->errorMessage : '';
$error_code = ! empty( $response->errorCode ) ? $response->errorCode : '';
if ( 'COMPLETE' === $status ) {
$critical_css = isset( $response->criticalCss ) ? $response->criticalCss : array();
foreach ( $critical_css as $hash => $css_value ) {
$hash_object = $this->get_queue_item_by_hash( $hash );
if ( ! empty( $css_value ) && ! empty( $hash_object ) ) {
$type = ! empty( $hash_object->type ) ? $hash_object->type : '';
$singular = ! empty( $hash_object->singular ) ? $hash_object->singular : '';
$used_css_path = $this->used_css_path( $type );
// Create the css file.
$status_file = $fs->write( $used_css_path, apply_filters( 'wphb_used_css', $css_value ) );
// Create the css file for mobile.
if ( 'asynchronously' === $this->get_critical_css_type() ) {
$mobile_css_key = $hash . '-mobile';
$mobile_css = isset( $critical_css->$mobile_css_key ) ? $critical_css->$mobile_css_key : '';
if ( ! empty( $mobile_css ) ) {
$used_css_path_mobile = $this->used_css_path( $type . '-mobile' );
$fs->write( $used_css_path_mobile, apply_filters( 'wphb_mobile_used_css', $mobile_css ) );
}
}
// If singular page.
if ( ! empty( $singular ) ) {
update_post_meta( $singular, self::CRITICAL_POST_META_KEY, 'complete' );
}
}
$this->update_item_in_persistent_queue( $hash, 'complete', '', $status, $error_message, $error_code );
}
} elseif ( 'ERROR' === $status && ! empty( $urls ) ) {
foreach ( $urls as $hash => $url_value ) {
$hash_object = $this->get_queue_item_by_hash( $hash );
if ( ! empty( $hash_object ) ) {
$singular = ! empty( $hash_object->singular ) ? $hash_object->singular : '';
// If singular page.
if ( ! empty( $singular ) ) {
$this->delete_post_meta_for_single_post( $singular );
update_post_meta( $singular, self::CRITICAL_POST_META_KEY, 'error' );
}
}
$this->update_item_in_persistent_queue( $hash, 'complete', '', $status, $error_message, $error_code );
}
} elseif ( 'ERROR' === $status ) {
$this->update_item_for_general_api_error( $id, $error_message, $error_code );
}
} else {
// If any API error occurs.
$api_error = $response->get_error_message();
$this->update_item_for_general_api_error( $id, $api_error, $api_error );
}
}
/**
* Function to update the item if there is a general API error, e.g. job not found.
*
* @param int $id Job ID.
* @param string $error_message Error message.
* @param string $error_code Error code.
*/
public function update_item_for_general_api_error( $id, $error_message, $error_code ) {
Utils::get_module( 'minify' )->log( 'There is an error in fetching data from the critical api ' . $error_message );
$queue_by_ids = $this->get_queue_items_by_job_id( $id );
foreach ( $queue_by_ids as $hash => $id_value ) {
$singular = ! empty( $id_value->singular ) ? $id_value->singular : '';
// If singular page.
if ( ! empty( $singular ) ) {
$this->delete_post_meta_for_single_post( $singular );
update_post_meta( $singular, self::CRITICAL_POST_META_KEY, 'error' );
}
$this->update_item_in_persistent_queue( $hash, 'complete', '', 'ERROR', $error_message, $error_code );
}
}
/**
* Function to delete all the critical css files.
*/
public function delete_css_cache_files() {
global $wphb_fs;
if ( ! $wphb_fs ) {
$wphb_fs = Filesystem::instance();
}
$directory = 'critical-css';
$is_network_admin = false;
if ( is_multisite() && isset( $_SERVER['HTTP_REFERER'] ) ) {
$is_network_admin = preg_match( '#^' . network_admin_url() . '#i', $_SERVER['HTTP_REFERER'] );
}
// For multisite we need to set this to null.
if ( is_multisite() && ! $is_network_admin ) {
$current_blog = get_site( get_current_blog_id() );
$directory .= $current_blog->path;
}
return $wphb_fs->purge( $directory );
}
/**
* Function to get the singular css.
*
* @param int $post_id Post ID.
*/
public function get_single_post_critical_css_status( $post_id ) {
return get_post_meta( $post_id, self::CRITICAL_POST_META_KEY, true );
}
/**
* Function to create critical css file for post.
*
* @param int $id Post ID.
*/
public function create_post_css_file( $id ) {
$type = $this->make_post_type_key( $id );
$req_url = get_permalink( $id );
update_post_meta( $id, self::CRITICAL_POST_META_KEY, '' );
$this->add_item( $req_url, $type, $id );
// If new Item is pushed.
if ( $this->persist_queue_to_db( true ) ) {
// Delete Existing CSS File.
$this->unlink_generated_critical_css_file( $type, $id );
update_post_meta( $id, self::CRITICAL_POST_META_KEY, 'processing' );
// Fire the cron.
$this->generate_critical_for_queue();
}
return true;
}
/**
* Function to create critical css file for post.
*
* @param int $id Post ID.
*/
public function recreate_post_css_file( $id ) {
return $this->create_post_css_file( $id );
}
/**
* Delete a generated critical css file.
*
* @param string $type Post type.
* @param int $post_id Post ID.
*/
public function unlink_generated_critical_css_file( $type, $post_id ) {
$css_paths = array(
$this->used_css_path( $type ),
$this->used_css_path( $type . '-mobile' ),
);
$clear_cache = false;
foreach ( $css_paths as $css_path ) {
if ( $this->is_hb_critical_css_path( $css_path ) && file_exists( $css_path ) ) {
unlink( $css_path );
$clear_cache = true;
}
}
if ( $clear_cache && $post_id ) {
do_action( 'wphb_clear_page_cache', $post_id ); // Clear page cache for the supplied post.
}
}
/**
* Check if the path is a critical css path.
*
* @param string $path Path.
*
* @return bool
*/
public function is_hb_critical_css_path( $path ) {
return false !== strpos( $path, 'wphb-cache' ) && false !== strpos( $path, 'critical-css' );
}
/**
* Function to revert critical css file for post.
*
* @param int $id Post ID.
*/
public function revert_post_css_file( $id ) {
// Delete Existing CSS FIle.
$type = $this->make_post_type_key( $id );
$this->unlink_generated_critical_css_file( $type, $id );
$this->delete_post_meta_for_single_post( $id );
return true;
}
/**
* Delete post meta key for single post.
*
* @param int $id Post ID.
*/
public function delete_post_meta_for_single_post( $id ) {
delete_post_meta( $id, self::CRITICAL_POST_META_KEY );
return true;
}
/**
* Deletes all the post meta critical key.
*/
public function delete_all_post_meta_for_critical() {
global $wpdb;
$post_table = $wpdb->prefix . 'postmeta';
$wpdb->delete( $post_table, array( 'meta_key' => self::CRITICAL_POST_META_KEY ) );
}
/**
* Skip page type selected in settings.
*
* @since 3.6.0
* @access public
* @param bool $type Post Type.
*
* @return bool
*/
public function skip_page_type( $type ) {
if ( apply_filters( 'wphb_should_skip_page_type', false, $type ) ) {
return true;
}
$minify_options = Settings::get_settings( 'minify' );
$critical_page_types = $minify_options['critical_page_types'];
$critical_skipped_custom_post_types = $minify_options['critical_skipped_custom_post_types'];
$all_pages_type = Page_Cache::get_page_types( true );
if ( $type ) {
$type = $this->get_mapped_type_name_for_skipping( $type );
if ( ! empty( $critical_skipped_custom_post_types ) && in_array( $type, $critical_skipped_custom_post_types, true ) ) {
return true;
}
if ( in_array( $type, $all_pages_type, true ) && ! in_array( $type, $critical_page_types, true ) ) {
return true;
}
}
return false;
}
/**
* Fetch the queue by type.
*
* @param int $type Type.
*/
public function get_queue_item_by_type( $type ) {
if ( empty( $type ) ) {
return false;
}
$get_hash = $this->hash( $type );
return $this->get_queue_item_by_hash( $get_hash );
}
/**
* Gets all public post types.
*/
private function get_public_post_types() {
global $wpdb;
$post_types = get_post_types(
array(
'public' => true,
'publicly_queryable' => true,
)
);
$excluded_post_types = $this->get_excluded_post_types();
// Remove excluded post types from the retrieved ones.
$post_types = array_diff( $post_types, $excluded_post_types );
// Sanitize the post types for safe SQL use.
$post_types = esc_sql( $post_types );
$post_types = "'" . implode( "','", $post_types ) . "'";
// Retrieve the most recent post ID per post type.
$query = "SELECT MAX(ID) as ID, post_type
FROM (
SELECT ID, post_type
FROM $wpdb->posts
WHERE post_type IN ( $post_types )
AND post_status = 'publish'
ORDER BY post_date DESC
) AS posts
GROUP BY post_type";
$results = $wpdb->get_results( $query );
// Return results or an empty array if there's an error.
return ! is_wp_error( $results ) ? $results : array();
}
/**
* Gets all public post types that needs to be excluded.
*/
public function get_excluded_post_types() {
/**
* List of post types to be excluded.
*/
$excluded_post_types = array(
'blocks',
'cms_block',
'elementor_library',
'fl-builder-template',
'fusion_template',
'jet-woo-builder',
'karma-slider',
'oceanwp_library',
'slider',
'tbuilder_layout',
'tbuilder_layout_part',
'tt-gallery',
'web-story',
'xlwcty_thankyou',
);
/**
* Filter the excluded post types.
*
* @param array $excluded_post_types Excluded post types.
*/
return (array) apply_filters( 'wphb_css_excluded_post_types', $excluded_post_types );
}
/**
* Gets all public taxonomies.
*/
private function get_public_taxonomies() {
global $wpdb;
$available_taxonomies = get_taxonomies(
array(
'public' => true,
'publicly_queryable' => true,
)
);
$excluded_taxonomies = $this->get_excluded_taxonomies();
// Remove excluded taxonomies from the available list.
$filtered_taxonomies = array_diff( $available_taxonomies, $excluded_taxonomies );
// Sanitize and format the remaining taxonomies for SQL query.
$sanitized_taxonomies = esc_sql( $filtered_taxonomies );
$taxonomy_list = "'" . implode( "','", $sanitized_taxonomies ) . "'";
// Build the query to fetch the term with the highest term_id per taxonomy.
$query = "SELECT MAX( term_id ) AS ID, taxonomy
FROM (
SELECT term_id, taxonomy
FROM $wpdb->term_taxonomy
WHERE taxonomy IN ( $taxonomy_list )
AND count > 0
) AS taxonomies
GROUP BY taxonomy";
// Execute the query and return the results.
$results = $wpdb->get_results( $query );
$results = ! is_wp_error( $results ) ? $results : array();
return apply_filters( 'wphb_public_taxonomies_results', $results );
}
/**
* Gets all public taxonomies that needs to be excluded.
*/
public function get_excluded_taxonomies() {
$excluded_taxonomies = array(
'attachment_category',
'coupon_campaign',
'element_category',
'karma-slider-category',
'mediamatic_wpfolder',
'post_format',
'product_shipping_class',
'truethemes-gallery-category',
);
/**
* Filter the excluded taxonomies.
*
* @param array $excluded_taxonomies Excluded taxonomies.
*/
return (array) apply_filters( 'wphb_css_excluded_taxonomies', $excluded_taxonomies );
}
/**
* Get the page id.
*
* @return string
*/
public function get_singular_page_type() {
global $wp_query;
$page_type = false;
if ( $wp_query->is_page ) {
$page_type = 'page-' . $wp_query->post->ID;
} elseif ( $wp_query->is_single ) {
$page_type = $this->make_post_type_key( $wp_query->post->ID );
}
return $page_type;
}
/**
* Get the page id.
*
* @return string
*/
private function get_singular_page_id() {
global $wp_query;
$page_id = false;
if ( $wp_query->is_page || $wp_query->is_single ) {
$page_id = $wp_query->post->ID;
}
return $page_id;
}
/**
* Function to ignore types.
*
* @param string $type Page type.
*/
public function ignore_types( $type ) {
$ignore_types = array( '404' );
if ( in_array( $type, $ignore_types, true ) ) {
return true;
}
return false;
}
/**
* This notice is displayed when the critical CSS generation is complete.
*
* @since 3.6.0
*/
public function critical_css_generation_complete_notice() {
$wphb_cs_process_queue = $this->get_queue_option();
$wphb_cs_created_css_log_update = array();
$update_css_log = false;
$message = '';
$group_id = null;
if ( $wphb_cs_process_queue && is_array( $wphb_cs_process_queue ) ) {
foreach ( $wphb_cs_process_queue as $hash => $queue_array ) {
$keep_queue_key = true;
if ( 'complete' === $queue_array->status && 0 === $queue_array->display_notice ) {
if ( 'COMPLETE' !== $queue_array->result ) {
if ( $group_id !== $queue_array->id ) {
$group_id = $queue_array->id;
$error = ! empty( $queue_array->error_message ) ? __( '<em> Error: ', 'wphb' ) . $queue_array->error_message . ' </em>' : $queue_array->result;
$message .= sprintf(
// translators: %1$s = error message.
__( '<br />%1$s', 'wphb' ),
'<strong>' . $error . '</strong>'
);
}
} else {
$keep_queue_key = false;
}
$update_css_log = true;
}
if ( $keep_queue_key ) {
$wphb_cs_created_css_log_update[ $hash ] = $queue_array;
}
}
if ( $update_css_log ) {
update_option( self::QUEUE_OPTION_ID, $wphb_cs_created_css_log_update );
// If all the statuses are completed.
if ( ! empty( $message ) ) {
$message = sprintf(
// translators: %1$s = message.
__( 'Critical CSS generation failed. Please review the below error to troubleshoot.%1$s', 'wphb' ),
$message
);
}
}
}
return $message;
}
/**
* Adds load css script.
*/
public function insert_load_css_script() {
$options = Utils::get_module( 'minify' )->get_options();
$is_async = 'asynchronously' === $options['critical_css_type'] || ( 'remove' === $options['critical_css_type'] && 'async_with_remove' === $options['critical_css_remove_type'] );
if ( ! $is_async ) {
return;
}
// Don't load on search page.
if ( is_search() ) {
return;
}
// Don't load on 404 page.
if ( is_404() ) {
return;
}
echo '<script>
/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
(function(w){"use strict";if(!w.loadCSS){w.loadCSS=function(){}}
var rp=loadCSS.relpreload={};rp.support=(function(){var ret;try{ret=w.document.createElement("link").relList.supports("preload")}catch(e){ret=!1}
return function(){return ret}})();rp.bindMediaToggle=function(link){var finalMedia=link.media||"all";function enableStylesheet(){link.media=finalMedia}
if(link.addEventListener){link.addEventListener("load",enableStylesheet)}else if(link.attachEvent){link.attachEvent("onload",enableStylesheet)}
setTimeout(function(){link.rel="stylesheet";link.media="only x"});setTimeout(enableStylesheet,3000)};rp.poly=function(){if(rp.support()){return}
var links=w.document.getElementsByTagName("link");for(var i=0;i<links.length;i++){var link=links[i];if(link.rel==="preload"&&link.getAttribute("as")==="style"&&!link.getAttribute("data-loadcss")){link.setAttribute("data-loadcss",!0);rp.bindMediaToggle(link)}}};if(!rp.support()){rp.poly();var run=w.setInterval(rp.poly,500);if(w.addEventListener){w.addEventListener("load",function(){rp.poly();w.clearInterval(run)})}else if(w.attachEvent){w.attachEvent("onload",function(){rp.poly();w.clearInterval(run)})}}
if(typeof exports!=="undefined"){exports.loadCSS=loadCSS}
else{w.loadCSS=loadCSS}}(typeof global!=="undefined"?global:this))
</script>';
}
/**
* This notice is displayed when the critical CSS generation is complete.
*
* @since 3.6.0
*/
public function critical_css_status_for_queue() {
if ( ! $this->is_active() ) {
return false;
}
$critical_css_log = $this->get_log_option();
if ( empty( $critical_css_log ) ) {
return false;
}
return $critical_css_log;
}
/**
* Get html data for status tag.
*
* @return bool|string
* @since 3.6.0
*/
public function get_html_for_status_tag() {
if ( ! Utils::is_member() || ! $this->is_active() ) {
return '<span id="critical_progress_tag"></span>';
}
$critical_css_log = $this->get_log_option();
$status = isset( $critical_css_log['status'] ) ? $critical_css_log['status'] : '';
if ( $this->is_active() && ( 'processing' === $status || 'pending' === $status ) ) {
$tag_display_value = esc_html__( 'Optimizing ', 'wphb' );
$sui_tag = 'sui-tag sui-tag-blue sui-tooltip sui-tooltip-constrained';
$sui_icon = 'sui-icon-loader sui-loading';
$tooltip_text = esc_html__( 'Generating Critical CSS, this could take about a minute.', 'wphb' );
} elseif ( $this->is_active() && 'complete' === $status ) {
$is_result_complete = 'COMPLETE' === $critical_css_log['result'];
$tag_display_value = $is_result_complete ? esc_html__( 'Optimized', 'wphb' ) : esc_html__( 'Error', 'wphb' );
$sui_tag = $is_result_complete ? 'sui-tag sui-tag-green sui-tooltip sui-tooltip-constrained' : 'sui-tag sui-tag-yellow';
$sui_icon = $is_result_complete ? 'sui-icon-info' : 'sui-icon-info sui-icon-error';
$tooltip_text = $is_result_complete ? esc_html__( 'Last Generated: ', 'wphb' ) . $critical_css_log['last_updated'] : '';
} else {
$tag_display_value = esc_html__( 'Unoptimized', 'wphb' );
$sui_tag = 'sui-tag sui-tag-grey sui-tooltip sui-tooltip-constrained';
$sui_icon = 'sui-icon-info';
$tooltip_text = esc_html__( 'Select settings and saves changes to generate Critical CSS', 'wphb' );
}
return sprintf(
// translators: %1$s = tooltip text, %2$s = sui tag, %3$s = info icon, %4$s = text for tag.
__( '<span id="critical_progress_tag" data-tooltip="%1$s" class="wphb_progress_tag %2$s"><span class="%3$s" aria-hidden="true"></span>%4$s</span>', 'wphb' ),
$tooltip_text,
$sui_tag,
$sui_icon,
$tag_display_value
);
}
/**
* Get processing queue data.
*/
private function get_queue_option() {
return get_option( self::QUEUE_OPTION_ID, array() );
}
/**
* Get status tag data.
*/
private function get_log_option() {
return get_option( 'wphb_critical_css_log', array() );
}
/**
* Get key for single post type.
*
* @param int $id Post ID.
*/
private function make_post_type_key( $id ) {
return get_post_type( $id ) . '-' . $id;
}
/**
* Checks if cron is disabled or not. Returns true if disabled false otherwise.
*/
private function is_cron_disabled() {
return defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON;
}
/**
* Schedule the cron to fetch the generated critical css.
*
* @return void
*/
private function schedule_generate_critical_cron() {
$queue_pending = $this->get_persistent_queue( array( 'pending' ) ); // Get all the pending Queue.
if ( ! empty( $queue_pending ) ) {
if ( $this->is_cron_disabled() ) {
$this->generate_critical_for_queue();
} else {
$this->maybe_schedule_generate_critical_cron();
}
}
}
/**
* Returns the formatted error message for MP.
*
* @param array $item_detail Item detail.
*/
public function get_error_code_from_log( $item_detail = array() ) {
$critical_css_log = ! empty( $item_detail ) ? $item_detail : $this->get_log_option();
$error_code = '';
if ( isset( $critical_css_log['result'] ) && 'ERROR' === $critical_css_log['result'] ) {
$error_code = isset( $critical_css_log['error_code'] ) ? $critical_css_log['error_code'] : 'unknown';
}
return $error_code;
}
/**
* Get the critical css type.
*/
public function get_critical_css_type() {
$options = Utils::get_module( 'minify' )->get_options();
return ! empty( $options['critical_css_type'] ) ? $options['critical_css_type'] : 'remove';
}
/**
* Check if critical css for mobile is exist for current page.
*
* @param string $type Type.
*/
public function maybe_get_critical_css_path_for_mobile( $type ) {
if ( ! $this->is_mobile_critical_css_allowed() ) {
return $type;
}
// Check if the request is coming from a mobile device.
if ( Page_Cache::is_mobile_agent() ) {
$file_name_for_mobile = $type . '-mobile';
$critical_css_path = $this->used_css_path( $file_name_for_mobile );
// If a mobile-specific CSS file exists, just return the name.
$critical_css_content = file_exists( $critical_css_path );
if ( $critical_css_content ) {
return $file_name_for_mobile;
}
}
return $type;
}
/**
* Check if mobile critical CSS is allowed.
*
* @return bool
*/
public function is_mobile_critical_css_allowed() {
return 'asynchronously' === $this->get_critical_css_type() && ! Fast_CGI::is_fast_cgi_enabled();
}
/**
* Get item for critical calculate api.
*
* @param object $item Item.
* @param bool $is_mobile Is mobile.
*/
public function get_item_data_for_critical_calculate_api( $item, $is_mobile = false ) {
return array(
'url' => $item->url,
'width' => $is_mobile ? 414 : 1920,
'height' => $is_mobile ? 915 : 1080,
);
}
/**
* Get the critical mode for MP.
*/
public function get_critical_mode_for_mp() {
$options = Utils::get_module( 'minify' )->get_options();
if ( 'asynchronously' === $options['critical_css_type'] ) {
return 'load_stylesheet_on_user_interaction' === $options['above_fold_load_stylesheet_method'] ? 'abovefold_delay' : 'abovefold_async';
}
return 'remove_unused' === $options['critical_css_remove_type'] ? 'fullpage_remove' : 'fullpage_delay';
}
/**
* Filter minified resources.
*
* @param bool $value Current value.
* @param string $handle Resource handle.
* @param string $type Script or style.
* @param string $url Script URL.
*
* @return bool
*/
public function filter_resource_minify( $value, $handle, $type, $url ) {
if ( 'styles' === $type ) {
$patterns = Utils::get_module( 'exclusions' )->get_combined_asset_path_exclusion_list_for_critical_css();
if ( empty( $patterns ) ) {
return $value;
}
$combined_pattern = '#(' . implode( '|', array_map( 'preg_quote', $patterns ) ) . ')#i';
if ( preg_match( $combined_pattern, $url ) ) {
$this->excluded_stylesheet_ids[] = $handle;
}
}
return $value;
}
}