WebPageCache
7 months ago
external
2 months ago
ExcludeJsFromDelay.php
2 years ago
OptimizerBanner.php
11 months ago
OptimizerBase.php
2 years ago
OptimizerCSSMin.php
2 years ago
OptimizerCache.php
1 year ago
OptimizerCacheStructure.php
2 years ago
OptimizerCriticalCss.php
1 year ago
OptimizerElementor.php
2 years ago
OptimizerFonts.php
2 years ago
OptimizerImages.php
1 year ago
OptimizerLogger.php
1 year ago
OptimizerMain.php
2 months ago
OptimizerOnInit.php
11 months ago
OptimizerScripts.php
2 years ago
OptimizerSettings.php
2 months ago
OptimizerStyles.php
2 years ago
OptimizerUrl.php
1 year ago
OptimizerUtils.php
2 months ago
OptimizerWhiteLabel.php
2 years ago
OptimizerFonts.php
300 lines
| 1 | <?php |
| 2 | |
| 3 | namespace TenWebOptimizer; |
| 4 | |
| 5 | /* |
| 6 | * Thin wrapper around css minifiers to avoid rewriting a bunch of existing code. |
| 7 | */ |
| 8 | if (!defined('ABSPATH')) { |
| 9 | exit; |
| 10 | } |
| 11 | |
| 12 | class OptimizerFonts extends OptimizerBase |
| 13 | { |
| 14 | const TWO_PRELOAD_LINK_TYPES = [ |
| 15 | 'preload', |
| 16 | 'preconnect' |
| 17 | ]; |
| 18 | |
| 19 | /** |
| 20 | * @var bool|mixed|void |
| 21 | */ |
| 22 | private $linksToPreload; |
| 23 | |
| 24 | private $font_swap; |
| 25 | |
| 26 | private $two_load_fonts_via_webfont; |
| 27 | |
| 28 | private $webFont_list; |
| 29 | |
| 30 | private $google_subsets; |
| 31 | |
| 32 | private $google_fonts; |
| 33 | |
| 34 | private $fontsExtensions = [ |
| 35 | 'eot' => 'font/eot', |
| 36 | 'ttf' => 'font/ttf', |
| 37 | 'woff' => 'font/woff', |
| 38 | 'woff2' => 'font/woff2', |
| 39 | ]; |
| 40 | |
| 41 | private $styleExtensions = [ |
| 42 | 'css' => 'text/css', |
| 43 | ]; |
| 44 | |
| 45 | private $scriptExtensions = [ |
| 46 | 'js' => 'application/javascript', |
| 47 | ]; |
| 48 | |
| 49 | private $videoExtensions = [ |
| 50 | 'avi' => 'video/x-msvideo', |
| 51 | 'mpeg' => 'video/mpeg', |
| 52 | 'ogv' => 'video/ogg', |
| 53 | 'ts' => 'video/mp2t', |
| 54 | 'webm' => 'video/webm', |
| 55 | '3gp' => 'video/3gpp', |
| 56 | '3g2' => 'video/3gpp2', |
| 57 | ]; |
| 58 | |
| 59 | /** |
| 60 | * @var false|mixed|void |
| 61 | */ |
| 62 | private $linksToPreconnect; |
| 63 | |
| 64 | public function addPreload($option, $type) |
| 65 | { |
| 66 | if (empty($option)) { |
| 67 | return true; |
| 68 | } |
| 69 | |
| 70 | $links = $this->convertOptionToArray($option); |
| 71 | $this->injectLinks($type, $links); |
| 72 | |
| 73 | return true; |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * @param string $preloadType |
| 78 | * @param array $links |
| 79 | */ |
| 80 | private function injectLinks($preloadType, $links) |
| 81 | { |
| 82 | if (in_array($preloadType, self::TWO_PRELOAD_LINK_TYPES, false)) { // phpcs:ignore |
| 83 | if (!empty($links)) { |
| 84 | $preloadString = ''; |
| 85 | |
| 86 | foreach ($links as $link) { |
| 87 | $linkWithoutQueryArgs = strpos($link, '?') !== false ? substr($link, 0, strpos($link, '?')) : $link; |
| 88 | $linkPath = pathinfo($linkWithoutQueryArgs); |
| 89 | |
| 90 | if (isset($linkPath['extension'])) { |
| 91 | $type = $this->getLinkType($linkPath['extension']); |
| 92 | |
| 93 | if ($type !== false) { |
| 94 | $preloadString .= '<link rel="' . $preloadType . '" as="' . $type[0] . '" href="' . $link . '" type="' . $type[1] . '" crossorigin="anonymous">'; |
| 95 | } else { |
| 96 | $preloadString .= '<link rel="' . $preloadType . '" href="' . $link . '" crossorigin="anonymous">'; |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | if (!empty($preloadString)) { |
| 103 | $this->content = OptimizerUtils::inject_in_html($this->content, $preloadString, [ '<head', 'after_tag' ]); |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Get all google fonts from html combine them add preconnect and remove old ones |
| 110 | * |
| 111 | * @return bool |
| 112 | */ |
| 113 | public function combineGoogleFonts() |
| 114 | { |
| 115 | if (!$this->font_swap) { |
| 116 | return true; |
| 117 | } |
| 118 | |
| 119 | //check if google fonts are in HTML |
| 120 | $google_fonts_pattern = '/<link(?:\s+(?:(?!href\s*=\s*)[^>])+)?(?:\s+href\s*=\s*([\'"])(?<url>(?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>/Umsi'; |
| 121 | $result = preg_match_all($google_fonts_pattern, $this->content, $fonts, PREG_SET_ORDER); |
| 122 | |
| 123 | if (empty($result)) { |
| 124 | return true; |
| 125 | } |
| 126 | |
| 127 | //add preconnect for google fonts |
| 128 | $this->addPreconnect(); |
| 129 | |
| 130 | //if more than 1 font is found parse and combine them into 1 <link> tag |
| 131 | if (count($fonts) > 1) { |
| 132 | $this->parse_google_fonts($fonts); |
| 133 | $google_font_tag = $this->get_combined_font_tag(); |
| 134 | $this->content = OptimizerUtils::inject_in_html($this->content, $google_font_tag, [ '<head', 'after_tag' ]); |
| 135 | |
| 136 | foreach ($fonts as $font) { |
| 137 | $this->remove_from_html($font[0]); |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | /** |
| 143 | * Add preconnect for google fonts |
| 144 | * |
| 145 | * @return bool |
| 146 | */ |
| 147 | public function addPreconnect() |
| 148 | { |
| 149 | // check if preconnect already exists |
| 150 | $preconnect_pattern = "/((<link[^>]*rel=(['\"])preconnect(['\"])[^>]*href=(['\"])(?:https?:)\/\/fonts.gstatic.com[^>]*)|(<link[^>]*href=(['\"])(?:https?:)\/\/fonts.gstatic.com[^>]*rel=(['\"])preconnect(['\"])[^>]*))/Umsi"; |
| 151 | $result = preg_match_all($preconnect_pattern, $this->content, $preconnect, PREG_SET_ORDER); |
| 152 | |
| 153 | if (!empty($result)) { |
| 154 | return true; |
| 155 | } |
| 156 | |
| 157 | //if everything is fine add precoonect to final html |
| 158 | $preconnect_string = '<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>'; |
| 159 | $this->content = OptimizerUtils::inject_in_html($this->content, $preconnect_string, [ '<head', 'after_tag' ]); |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * Get Combined tag for all found google fonts |
| 164 | * |
| 165 | * @return string |
| 166 | */ |
| 167 | protected function get_combined_font_tag() |
| 168 | { |
| 169 | return '<link rel="preload" as="style" href="https://fonts.googleapis.com/css?family=' . $this->google_fonts . $this->google_subsets . '&display=swap"> |
| 170 | <link rel="stylesheet" media="none" onload="this.onload=null;this.removeAttribute(\'media\');" href="https://fonts.googleapis.com/css?family=' . $this->google_fonts . $this->google_subsets . '&display=swap"> |
| 171 | <noscript> |
| 172 | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=' . $this->google_fonts . $this->google_subsets . '&display=swap"> |
| 173 | </noscript>'; |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * Parse all found google fonts |
| 178 | */ |
| 179 | protected function parse_google_fonts($fonts) |
| 180 | { |
| 181 | $fonts_array = []; |
| 182 | $subsets_array = []; |
| 183 | |
| 184 | foreach ($fonts as $font) { |
| 185 | $url = html_entity_decode($font[2]); |
| 186 | $query = wp_parse_url($url, PHP_URL_QUERY); |
| 187 | |
| 188 | if (empty($query)) { |
| 189 | return; |
| 190 | } |
| 191 | |
| 192 | $font = wp_parse_args($query); |
| 193 | |
| 194 | if (isset($font['family'])) { |
| 195 | $font_family = $font['family']; |
| 196 | $font_family = rtrim($font_family, '%7C'); |
| 197 | $font_family = rtrim($font_family, '|'); |
| 198 | // Add font to the collection. |
| 199 | $fonts_array[] = rawurlencode(htmlentities($font_family)); |
| 200 | } |
| 201 | |
| 202 | // Add subset to collection. |
| 203 | if (isset($font['subset'])) { |
| 204 | $subsets_array[] = rawurlencode(htmlentities($font['subset'])); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | // Concatenate fonts tag. |
| 209 | $this->google_subsets = ! empty($subsets_array) ? '&subset=' . implode(',', array_filter(array_unique($subsets_array))) : ''; |
| 210 | $this->google_fonts = ! empty($fonts_array) ? implode('%7C', array_filter(array_unique($fonts_array))) : ''; |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Returns array of as and type |
| 215 | * |
| 216 | * @return array|bool |
| 217 | */ |
| 218 | private function getLinkType($extension) |
| 219 | { |
| 220 | if (isset($this->fontsExtensions[$extension])) { |
| 221 | return ['font', $this->fontsExtensions[$extension]]; |
| 222 | } |
| 223 | |
| 224 | if (isset($this->styleExtensions[$extension])) { |
| 225 | return ['style', $this->styleExtensions[$extension]]; |
| 226 | } |
| 227 | |
| 228 | if (isset($this->scriptExtensions[$extension])) { |
| 229 | return ['script', $this->scriptExtensions[$extension]]; |
| 230 | } |
| 231 | |
| 232 | if (isset($this->videoExtensions[$extension])) { |
| 233 | return ['video', $this->videoExtensions[$extension]]; |
| 234 | } |
| 235 | |
| 236 | return false; |
| 237 | } |
| 238 | |
| 239 | public function getcontent() |
| 240 | { |
| 241 | return $this->content; |
| 242 | } |
| 243 | |
| 244 | public function read($options) |
| 245 | { |
| 246 | global $TwoSettings; |
| 247 | $this->linksToPreload = $TwoSettings->get_settings('two_fonts_to_preload', ''); |
| 248 | $this->linksToPreconnect = $TwoSettings->get_settings('two_fonts_to_preconnect', ''); |
| 249 | $this->font_swap = empty($TwoSettings->get_settings('two_async_font', false)) ? false : true; |
| 250 | $this->two_load_fonts_via_webfont = empty($TwoSettings->get_settings('two_load_fonts_via_webfont', false)) ? false : true; |
| 251 | |
| 252 | if (isset($options['webFont_list'])) { |
| 253 | $this->webFont_list = $options['webFont_list']; |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | public function optimize() |
| 258 | { |
| 259 | $this->addPreload($this->linksToPreload, 'preload'); |
| 260 | $this->addPreload($this->linksToPreconnect, 'preconnect'); |
| 261 | $this->combineGoogleFonts(); |
| 262 | |
| 263 | if ($this->two_load_fonts_via_webfont && is_array($this->webFont_list) && !empty($this->webFont_list)) { |
| 264 | $this->add_WebFont(); |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | public function add_WebFont() |
| 269 | { |
| 270 | $webFont_js = ' |
| 271 | <script ' . esc_attr(OptimizerScripts::TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE) . ' ' . OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE . '> |
| 272 | WebFontConfig = { |
| 273 | google: {families: ' . |
| 274 | json_encode($this->webFont_list) // phpcs:ignore |
| 275 | . "} |
| 276 | }; |
| 277 | (function () { |
| 278 | var wf = document.createElement('script'); |
| 279 | wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js'; |
| 280 | wf.type = 'text/javascript'; |
| 281 | wf.async = 'true'; |
| 282 | var s = document.getElementsByTagName('script')[0]; |
| 283 | s.parentNode.insertBefore(wf, s); |
| 284 | })(); |
| 285 | </script> |
| 286 | "; |
| 287 | $this->content = OptimizerUtils::inject_in_html($this->content, $webFont_js, [ '<head', 'after_tag' ]); |
| 288 | } |
| 289 | |
| 290 | public function cache() |
| 291 | { |
| 292 | // TODO: Implement cache() method. |
| 293 | } |
| 294 | |
| 295 | private function convertOptionToArray($option) |
| 296 | { |
| 297 | return array_filter(array_map('trim', explode(',', $option))); |
| 298 | } |
| 299 | } |
| 300 |