options = $options;
}
/**
* Helper for getting a singleton instance. While being an
* anti-pattern generally, it comes in handy for now from a
* readability/maintainability perspective, until we get some
* proper dependency injection going.
*
* @return self
*/
public static function instance()
{
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public function run()
{
if ( is_admin() ) {
if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
add_action( 'network_admin_menu', array( $this, 'admin_menu' ) );
} else {
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
}
add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_extra_tab' ) );
} else {
add_action( 'wp', array( $this, 'run_on_frontend' ) );
}
}
public function set_options( array $options )
{
$this->options = $options;
return $this;
}
public static function fetch_options()
{
$value = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings' );
if ( empty( $value ) ) {
// Fallback to returning defaults when no stored option exists yet.
$value = autoptimizeConfig::get_ao_extra_default_options();
}
return $value;
}
public function disable_emojis()
{
// Removing all actions related to emojis!
remove_action( 'admin_print_styles', 'print_emoji_styles' );
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
// Removes TinyMCE emojis.
add_filter( 'tiny_mce_plugins', array( $this, 'filter_disable_emojis_tinymce' ) );
// Removes emoji dns-preftech.
add_filter( 'wp_resource_hints', array( $this, 'filter_remove_emoji_dns_prefetch' ), 10, 2 );
}
public function filter_disable_emojis_tinymce( $plugins )
{
if ( is_array( $plugins ) ) {
return array_diff( $plugins, array( 'wpemoji' ) );
} else {
return array();
}
}
public function filter_remove_qs( $src )
{
if ( strpos( $src, '?ver=' ) ) {
$src = remove_query_arg( 'ver', $src );
}
return $src;
}
public function extra_async_js( $in )
{
$exclusions = array();
if ( ! empty( $in ) ) {
$exclusions = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $in ) ) ), '' );
}
$settings = $this->options['autoptimize_extra_text_field_3'];
$async = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $settings ) ) ), '' );
$attr = apply_filters( 'autoptimize_filter_extra_async', 'async' );
foreach ( $async as $k => $v ) {
$async[ $k ] = $attr;
}
// Merge exclusions & asyncs in one array and return to AO API.
$merged = array_merge( $exclusions, $async );
return $merged;
}
public function run_on_frontend()
{
// only run the Extra optimizations on frontend if general conditions
// for optimizations are met, this to ensure e.g. removing querystrings
// is not done when optimizing for logged in users is off, breaking
// some pagebuilders (Divi & Elementor).
if ( false === autoptimizeMain::should_buffer() ) {
return;
}
$options = $this->options;
// Disable emojis if specified.
if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) ) {
$this->disable_emojis();
}
// Remove version query parameters.
if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) ) {
add_filter( 'script_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
add_filter( 'style_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
}
// Avoiding conflicts of interest when async-javascript plugin is active!
$async_js_plugin_active = autoptimizeUtils::is_plugin_active( 'async-javascript/async-javascript.php' );
if ( ! empty( $options['autoptimize_extra_text_field_3'] ) && ! $async_js_plugin_active ) {
add_filter( 'autoptimize_filter_js_exclude', array( $this, 'extra_async_js' ), 10, 1 );
}
// Optimize google fonts!
if ( ! empty( $options['autoptimize_extra_radio_field_4'] ) && ( '1' !== $options['autoptimize_extra_radio_field_4'] ) ) {
add_filter( 'wp_resource_hints', array( $this, 'filter_remove_gfonts_dnsprefetch' ), 10, 2 );
add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_google_fonts' ), 10, 1 );
add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_google_fonts' ), 10, 1 );
}
// Preconnect!
if ( ! empty( $options['autoptimize_extra_text_field_2'] ) || has_filter( 'autoptimize_extra_filter_tobepreconn' ) ) {
add_filter( 'wp_resource_hints', array( $this, 'filter_preconnect' ), 10, 2 );
}
// Preload!
if ( ! empty( $options['autoptimize_extra_text_field_7'] ) ) {
add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_preload' ), 10, 2 );
}
}
public function filter_remove_emoji_dns_prefetch( $urls, $relation_type )
{
$emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' );
return $this->filter_remove_dns_prefetch( $urls, $relation_type, $emoji_svg_url );
}
public function filter_remove_gfonts_dnsprefetch( $urls, $relation_type )
{
return $this->filter_remove_dns_prefetch( $urls, $relation_type, 'fonts.googleapis.com' );
}
public function filter_remove_dns_prefetch( $urls, $relation_type, $url_to_remove )
{
$url_to_remove = (string) $url_to_remove;
if ( ! empty( $url_to_remove ) && 'dns-prefetch' === $relation_type ) {
$cnt = 0;
foreach ( $urls as $url ) {
if ( false !== strpos( $url, $url_to_remove ) ) {
unset( $urls[ $cnt ] );
}
$cnt++;
}
}
return $urls;
}
public function filter_optimize_google_fonts( $in )
{
// Extract fonts, partly based on wp rocket's extraction code.
$markup = preg_replace( '//Uis', '', $in );
preg_match_all( '#])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $markup, $matches );
$fonts_collection = array();
if ( ! $matches[2] ) {
return $in;
}
// Store them in $fonts array.
$i = 0;
foreach ( $matches[2] as $font ) {
if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) {
// Get fonts name.
$font = str_replace( array( '%7C', '%7c' ), '|', $font );
if ( strpos( $font, 'fonts.googleapis.com/css2' ) !== false ) {
// (Somewhat) change Google Fonts APIv2 syntax back to v1.
$font = str_replace( array( 'wght@', 'wght%40', ';', '%3B' ), array( '', '', ',', ',' ), $font );
}
$font = explode( 'family=', $font );
$font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array();
// Add font to $fonts[$i] but make sure not to pollute with an empty family!
$_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) );
if ( ! empty( $_thisfont ) ) {
$fonts_collection[ $i ]['fonts'] = $_thisfont;
// And add subset if any!
$subset = ( is_array( $font ) ) ? end( $font ) : '';
if ( false !== strpos( $subset, 'subset=' ) ) {
$subset = str_replace( array( '%2C', '%2c' ), ',', $subset );
$subset = explode( 'subset=', $subset );
$fonts_collection[ $i ]['subsets'] = explode( ',', $subset[1] );
}
}
// And remove Google Fonts.
$in = str_replace( $matches[0][ $i ], '', $in );
}
$i++;
}
$options = $this->options;
$fonts_markup = '';
if ( '2' === $options['autoptimize_extra_radio_field_4'] ) {
// Remove Google Fonts.
unset( $fonts_collection );
return $in;
} elseif ( '3' === $options['autoptimize_extra_radio_field_4'] || '5' === $options['autoptimize_extra_radio_field_4'] ) {
// Aggregate & link!
$fonts_string = '';
$subset_string = '';
foreach ( $fonts_collection as $font ) {
$fonts_string .= '|' . trim( implode( '|', $font['fonts'] ), '|' );
if ( ! empty( $font['subsets'] ) ) {
$subset_string .= ',' . trim( implode( ',', $font['subsets'] ), ',' );
}
}
if ( ! empty( $subset_string ) ) {
$subset_string = str_replace( ',', '%2C', ltrim( $subset_string, ',' ) );
$fonts_string = $fonts_string . '&subset=' . $subset_string;
}
$fonts_string = apply_filters( 'autoptimize_filter_extra_gfont_fontstring', str_replace( '|', '%7C', ltrim( $fonts_string, '|' ) ) );
// only add display parameter if there is none in $fonts_string (by virtue of the filter).
if ( strpos( $fonts_string, 'display=' ) === false ) {
$fonts_string .= apply_filters( 'autoptimize_filter_extra_gfont_display', '&display=swap' );
}
if ( ! empty( $fonts_string ) ) {
if ( '5' === $options['autoptimize_extra_radio_field_4'] ) {
$rel_string = 'rel="preload" as="style" onload="' . autoptimizeConfig::get_ao_css_preload_onload() . '"';
} else {
$rel_string = 'rel="stylesheet"';
}
$fonts_markup = '';
}
} elseif ( '4' === $options['autoptimize_extra_radio_field_4'] ) {
// Aggregate & load async (webfont.js impl.)!
$fonts_array = array();
foreach ( $fonts_collection as $_fonts ) {
if ( ! empty( $_fonts['subsets'] ) ) {
$_subset = implode( ',', $_fonts['subsets'] );
foreach ( $_fonts['fonts'] as $key => $_one_font ) {
$_one_font = $_one_font . ':' . $_subset;
$_fonts['fonts'][ $key ] = $_one_font;
}
}
$fonts_array = array_merge( $fonts_array, $_fonts['fonts'] );
}
$fonts_array = array_map( 'urldecode', $fonts_array );
$fonts_array = array_map(
function( $_f ) {
return trim( $_f, ',' );
},
$fonts_array
);
// type attrib on ';
$fonts_library_markup = '';
$in = substr_replace( $in, $fonts_library_markup . '', strpos( $in, '' ), strlen( '' ) );
}
// Replace back in markup.
$inject_point = apply_filters( 'autoptimize_filter_extra_gfont_injectpoint', '', $preload_polyfill . '