| 1 |
<?php |
| 2 |
if (!defined('ABSPATH')) die('No direct access allowed'); |
| 3 |
|
| 4 |
if (!class_exists('WP_Optimize_Minify_Functions')) { |
| 5 |
include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-functions.php'; |
| 6 |
} |
| 7 |
|
| 8 |
class WP_Optimize_Minify_Print { |
| 9 |
|
| 10 |
/** |
| 11 |
* Load the script using loadAsync JavaScript |
| 12 |
* |
| 13 |
* @param string $href |
| 14 |
* @param boolean $print |
| 15 |
* @return string|void |
| 16 |
*/ |
| 17 |
public static function async_script($href, $print = true) { |
| 18 |
$wpo_minify_options = wp_optimize_minify_config()->get(); |
| 19 |
$tag = '<script>' . "\n"; |
| 20 |
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''; |
| 21 |
$tag .= 'var wpo_server_info_js = ' . wp_json_encode(array("user_agent" => $user_agent)) . "\n"; |
| 22 |
|
| 23 |
$exclude_js_from_page_speed_tools = $wpo_minify_options['exclude_js_from_page_speed_tools']; |
| 24 |
$enable_defer_js = $wpo_minify_options['enable_defer_js']; |
| 25 |
$is_conditional_loading = $exclude_js_from_page_speed_tools && 'all' !== $enable_defer_js; |
| 26 |
if ($is_conditional_loading) { |
| 27 |
$tag .= 'if (!(navigator.userAgent + " " + wpo_server_info_js.user_agent).match(/' . implode("|", $wpo_minify_options['ualist']) .'/i)) {' . "\n"; |
| 28 |
} |
| 29 |
$tag .= " loadAsync('$href', null);" . "\n"; |
| 30 |
if ($is_conditional_loading) { |
| 31 |
$tag .= '}'; |
| 32 |
} |
| 33 |
$tag .= '</script>' . "\n"; |
| 34 |
|
| 35 |
if ($print) { |
| 36 |
echo $tag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is already escaped |
| 37 |
} else { |
| 38 |
return $tag; |
| 39 |
} |
| 40 |
} |
| 41 |
|
| 42 |
/** |
| 43 |
* Print a style that will be loaded async using 'preload' |
| 44 |
* |
| 45 |
* @param string $href |
| 46 |
* @param string $media |
| 47 |
* @return void |
| 48 |
*/ |
| 49 |
public static function async_style($href, $media = 'all') { |
| 50 |
// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- Not applicable for this function |
| 51 |
echo '<link rel="preload" href="'.esc_url($href).'" as="style" media="'.esc_attr($media).'" onload="this.onload=null;this.rel=\'stylesheet\'">' . "\n"; |
| 52 |
// fix for firefox not supporting preload |
| 53 |
echo '<link rel="stylesheet" href="'.esc_url($href).'" media="'.esc_attr($media).'">' . "\n"; |
| 54 |
echo '<noscript><link rel="stylesheet" href="'.esc_url($href).'" media="'.esc_attr($media).'"></noscript>' . "\n"; |
| 55 |
echo '<!--[if IE]><link rel="stylesheet" href="'.esc_url($href).'" media="'.esc_attr($media).'"><![endif]-->' . "\n"; |
| 56 |
// phpcs:enable |
| 57 |
} |
| 58 |
|
| 59 |
/** |
| 60 |
* Print a style that will be loaded by JS |
| 61 |
* |
| 62 |
* @param string $href |
| 63 |
* @return void |
| 64 |
*/ |
| 65 |
public static function exclude_style($href) { |
| 66 |
$wpo_minify_options = wp_optimize_minify_config()->get(); |
| 67 |
// make a stylesheet, hide from PageSpeedIndex |
| 68 |
$cssguid = 'wpo_min'.hash('adler32', $href); |
| 69 |
$tag = '<script>' . "\n"; |
| 70 |
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''; |
| 71 |
$tag .= 'var wpo_server_info_css = ' . wp_json_encode(array("user_agent" => $user_agent)) . "\n"; |
| 72 |
|
| 73 |
$exclude_css_from_page_speed_tools = $wpo_minify_options['exclude_css_from_page_speed_tools']; |
| 74 |
if ($exclude_css_from_page_speed_tools) { |
| 75 |
$tag .= 'if (!(navigator.userAgent + " " + wpo_server_info_css.user_agent).match(/'.implode('|', $wpo_minify_options['ualist']).'/i)){' . "\n"; |
| 76 |
} |
| 77 |
$tag .= ' var '.$cssguid.'=document.createElement("link");'.$cssguid.'.rel="stylesheet",'.$cssguid.'.type="text/css",'.$cssguid.'.media="async",'.$cssguid.'.href="'.$href.'",'.$cssguid.'.onload=function() {'.$cssguid.'.media="all"},document.getElementsByTagName("head")[0].appendChild('.$cssguid.');' . "\n"; |
| 78 |
if ($exclude_css_from_page_speed_tools) { |
| 79 |
$tag .= '}'; |
| 80 |
} |
| 81 |
$tag .= '</script>' . "\n"; |
| 82 |
echo $tag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is already escaped |
| 83 |
} |
| 84 |
|
| 85 |
/** |
| 86 |
* Inline a single style |
| 87 |
* |
| 88 |
* @param string $handle |
| 89 |
* @param string $href |
| 90 |
* @return boolean |
| 91 |
*/ |
| 92 |
public static function inline_style($handle, $href) { |
| 93 |
$wpo_minify_options = wp_optimize_minify_config()->get(); |
| 94 |
|
| 95 |
// font awesome processing, inline |
| 96 |
// download, minify, cache |
| 97 |
$tkey = 'css-'.hash('adler32', $href).'.css'; |
| 98 |
$json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); |
| 99 |
if (false === $json) { |
| 100 |
$json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $wpo_minify_options['enable_css_minification'], 'css', $handle); |
| 101 |
if ($wpo_minify_options['debug']) { |
| 102 |
echo "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($href) . " -->" . "\n"; |
| 103 |
} |
| 104 |
WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json); |
| 105 |
} |
| 106 |
|
| 107 |
// decode |
| 108 |
$res = json_decode($json, true); |
| 109 |
|
| 110 |
// add font-display |
| 111 |
// https://developers.google.com/web/updates/2016/02/font-display |
| 112 |
$res['code'] = str_ireplace('font-style:normal;', 'font-display:block;font-style:normal;', $res['code']); |
| 113 |
|
| 114 |
// inline css or fail |
| 115 |
if ($res['status']) { |
| 116 |
echo '<style type="text/css" media="all">' . "\n"; |
| 117 |
echo $res['code'] . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is already escaped |
| 118 |
echo '</style>' . "\n"; |
| 119 |
return true; |
| 120 |
} else { |
| 121 |
return false; |
| 122 |
} |
| 123 |
} |
| 124 |
|
| 125 |
/** |
| 126 |
* Print a normal style tag pointing the href |
| 127 |
* |
| 128 |
* @param string $href |
| 129 |
* @return void |
| 130 |
*/ |
| 131 |
public static function style($href) { |
| 132 |
echo '<link rel="stylesheet" href="'.esc_url($href).'" media="all">' . "\n"; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- Not applicable here |
| 133 |
} |
| 134 |
|
| 135 |
/** |
| 136 |
* Write header |
| 137 |
* |
| 138 |
* @param string $file |
| 139 |
* @param string $headers |
| 140 |
* @return void |
| 141 |
*/ |
| 142 |
public static function write_header($file, $headers) { |
| 143 |
file_put_contents($file, $headers); |
| 144 |
WP_Optimize_Minify_Cache_Functions::fix_permission_bits($file); |
| 145 |
} |
| 146 |
|
| 147 |
/** |
| 148 |
* Write CSS |
| 149 |
* |
| 150 |
* @param string $file |
| 151 |
* @param string $code |
| 152 |
* @param array $log |
| 153 |
* @return void |
| 154 |
*/ |
| 155 |
public static function write_combined_asset($file, $code, $log) { |
| 156 |
file_put_contents($file.'.json', wp_json_encode($log)); |
| 157 |
file_put_contents($file, $code); |
| 158 |
// permissions |
| 159 |
WP_Optimize_Minify_Cache_Functions::fix_permission_bits($file.'.json'); |
| 160 |
WP_Optimize_Minify_Cache_Functions::fix_permission_bits($file); |
| 161 |
|
| 162 |
if (function_exists('gzencode')) { |
| 163 |
file_put_contents($file.'.gz', gzencode(file_get_contents($file), 9)); |
| 164 |
WP_Optimize_Minify_Cache_Functions::fix_permission_bits($file.'.gz'); |
| 165 |
} |
| 166 |
|
| 167 |
// brotli static support |
| 168 |
if (function_exists('brotli_compress')) { |
| 169 |
file_put_contents($file.'.br', brotli_compress(file_get_contents($file), 11)); |
| 170 |
WP_Optimize_Minify_Cache_Functions::fix_permission_bits($file.'.br'); |
| 171 |
} |
| 172 |
} |
| 173 |
/** |
| 174 |
* Load async scripts with callback |
| 175 |
* |
| 176 |
* @return void |
| 177 |
*/ |
| 178 |
public static function add_load_async() { |
| 179 |
$min_or_not_internal = WP_Optimize()->get_min_or_not_internal_string(); |
| 180 |
$contents = file_get_contents(trailingslashit(WPO_PLUGIN_MAIN_PATH) . "js/loadAsync$min_or_not_internal.js"); |
| 181 |
echo "<script>$contents</script>\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is already escaped |
| 182 |
} |
| 183 |
|
| 184 |
/** |
| 185 |
* Defer CSS globally from the header (order matters) |
| 186 |
* Dev: https://www.filamentgroup.com/lab/async-css.html |
| 187 |
* |
| 188 |
* @return void |
| 189 |
*/ |
| 190 |
public static function add_load_css() { |
| 191 |
$min_or_not_internal = WP_Optimize()->get_min_or_not_internal_string(); |
| 192 |
$contents = file_get_contents(trailingslashit(WPO_PLUGIN_MAIN_PATH) . "js/loadCSS$min_or_not_internal.js"); |
| 193 |
echo "<script>$contents</script>" . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is already escaped |
| 194 |
} |
| 195 |
} |
| 196 |
|