class-wp-optimize-detect-minify-plugins.php
4 years ago
class-wp-optimize-minify-admin.php
3 years ago
class-wp-optimize-minify-cache-functions.php
3 years ago
class-wp-optimize-minify-commands.php
2 years ago
class-wp-optimize-minify-config.php
3 years ago
class-wp-optimize-minify-fonts.php
3 years ago
class-wp-optimize-minify-front-end.php
2 years ago
class-wp-optimize-minify-functions.php
3 years ago
class-wp-optimize-minify-load-url-task.php
3 years ago
class-wp-optimize-minify-preloader.php
3 years ago
class-wp-optimize-minify-print.php
2 years ago
class-wp-optimize-minify.php
2 years ago
class-wp-optimize-minify-fonts.php
306 lines
| 1 | <?php |
| 2 | if (!defined('ABSPATH')) die('No direct access allowed'); |
| 3 | |
| 4 | class WP_Optimize_Minify_Fonts { |
| 5 | |
| 6 | private static $fonts = array(); |
| 7 | |
| 8 | private static $subsets = array(); |
| 9 | |
| 10 | /** |
| 11 | * Get a list of Google fonts |
| 12 | * |
| 13 | * @return Array |
| 14 | */ |
| 15 | public static function get_google_fonts() { |
| 16 | // https://www.googleapis.com/webfonts/v1/webfonts?sort=alpha |
| 17 | $google_fonts_file = WPO_PLUGIN_MAIN_PATH.'google-fonts.json'; |
| 18 | if (is_file($google_fonts_file) && is_readable($google_fonts_file)) { |
| 19 | return json_decode(file_get_contents($google_fonts_file), true); |
| 20 | } |
| 21 | return array(); |
| 22 | } |
| 23 | |
| 24 | /** |
| 25 | * Check if the google font exist or not |
| 26 | * |
| 27 | * @param string $font |
| 28 | * @return boolean |
| 29 | */ |
| 30 | public static function concatenate_google_fonts_allowed($font) { |
| 31 | $gfonts_whitelist = self::get_google_fonts(); |
| 32 | |
| 33 | // normalize |
| 34 | $font = str_ireplace('+', ' ', strtolower($font)); |
| 35 | |
| 36 | return in_array($font, $gfonts_whitelist); |
| 37 | } |
| 38 | |
| 39 | /** |
| 40 | * Concatenates Google Fonts tags (http://fonts.googleapis.com/css?...) |
| 41 | * |
| 42 | * @param array $gfonts_array |
| 43 | * @return string|boolean |
| 44 | */ |
| 45 | public static function concatenate_google_fonts($gfonts_array) { |
| 46 | // Loop through fonts array |
| 47 | foreach ($gfonts_array as $font) { |
| 48 | self::parse_font_url($font); |
| 49 | } |
| 50 | self::convert_v1_font_specs_to_v2(); |
| 51 | $merge = self::build(); |
| 52 | $config = wp_optimize_minify_config(); |
| 53 | /** |
| 54 | * Filters wether to add display=swap to Google fonts urls |
| 55 | * |
| 56 | * @param boolean $display - Default to true |
| 57 | */ |
| 58 | if (apply_filters('wpo_minify_gfont_display_swap', $config->get('enable_display_swap'))) { |
| 59 | /** |
| 60 | * Filters the value of the display parameter. |
| 61 | * |
| 62 | * @param string $display_value - Default to 'swap'. https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display |
| 63 | */ |
| 64 | $merge.= '&display='.apply_filters('wpo_minify_gfont_display_type', 'swap'); |
| 65 | } |
| 66 | |
| 67 | if (!empty($merge)) return 'https://fonts.googleapis.com/css2?' . $merge; |
| 68 | |
| 69 | return false; |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Parses font url based on whether it is API version 1 or 2 |
| 74 | */ |
| 75 | private static function parse_font_url($font) { |
| 76 | if (false !== strpos($font, 'css?')) { |
| 77 | self::parse_font_api1_url($font); |
| 78 | } else { |
| 79 | self::parse_font_api2_url($font); |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * Parses google font api version 1 url |
| 85 | */ |
| 86 | private static function parse_font_api1_url($font) { |
| 87 | parse_str(parse_url(rtrim($font, '|'), PHP_URL_QUERY), $font_elements); |
| 88 | // Process each font family |
| 89 | foreach (explode('|', $font_elements['family']) as $font_family) { |
| 90 | // Separate font and sizes |
| 91 | $font_family = explode(':', $font_family); |
| 92 | // if the family wasn't added yet |
| 93 | if (!in_array($font_family[0], array_keys(self::$fonts))) { |
| 94 | self::$fonts[$font_family[0]]['specs'] = isset($font_family[1]) ? explode(',', rtrim($font_family[1], ',')) : array(); |
| 95 | } else { |
| 96 | // if the family was already added, and this new one has weights, merge with previous |
| 97 | if (isset($font_family[1])) { |
| 98 | if (isset(self::$fonts[$font_family[0]]['version']) && 'V2' == self::$fonts[$font_family[0]]['version']) { |
| 99 | self::$fonts[$font_family[0]]['specs'] = explode(',', rtrim($font_family[1], ',')); |
| 100 | } else { |
| 101 | self::$fonts[$font_family[0]]['specs'] = array_merge(self::$fonts[$font_family[0]]['specs'], explode(',', rtrim($font_family[1], ','))); |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | self::$fonts[$font_family[0]]['version'] = 'V1'; |
| 106 | } |
| 107 | |
| 108 | // Add subsets |
| 109 | if (isset($font_elements['subset'])) { |
| 110 | self::$subsets = array_merge(self::$subsets, explode(',', $font_elements['subset'])); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Parses google font api version 2 url |
| 116 | */ |
| 117 | private static function parse_font_api2_url($font) { |
| 118 | $parsed_url = parse_url($font, PHP_URL_QUERY); |
| 119 | $query_elements = explode('&', $parsed_url); |
| 120 | foreach ($query_elements as $element) { |
| 121 | $family_str = str_replace('family=', '', $element); |
| 122 | $family = explode(':', $family_str); |
| 123 | if (!empty($family)) { |
| 124 | $font_name = $family[0]; |
| 125 | $font_elements = isset($family[1]) ? explode('@', $family[1]) : ''; |
| 126 | if (!empty($font_elements) && !empty($font_elements[0]) && !empty($font_elements[1])) { |
| 127 | $font_styles = $font_elements[0]; |
| 128 | $font_units = explode(',', $font_elements[1]); |
| 129 | } |
| 130 | } else { |
| 131 | $font_name = $family_str; |
| 132 | continue; |
| 133 | } |
| 134 | |
| 135 | if (!isset(self::$fonts[$font_name])) { |
| 136 | self::$fonts[$font_name]['specs'] = array( |
| 137 | 'wght' => array(), |
| 138 | 'ital' => array(), |
| 139 | 'ital,wght' => array(), |
| 140 | ); |
| 141 | } |
| 142 | |
| 143 | if (!isset(self::$fonts[$font_name]['version'])) { |
| 144 | self::$fonts[$font_name]['version'] = 'V2'; |
| 145 | } |
| 146 | if (isset($font_styles) && isset($font_units) && isset($font_elements[1])) { |
| 147 | $font_units = explode(';', $font_elements[1]); |
| 148 | switch ($font_styles) { |
| 149 | case 'wght': |
| 150 | foreach ($font_units as $font_unit) { |
| 151 | if (!in_array($font_unit, self::$fonts[$font_name]['specs']['wght'])) { |
| 152 | array_push(self::$fonts[$font_name]['specs']['wght'], $font_unit); |
| 153 | } |
| 154 | } |
| 155 | break; |
| 156 | case 'ital': |
| 157 | foreach ($font_units as $font_unit) { |
| 158 | if (!in_array($font_unit, self::$fonts[$font_name]['specs']['ital'])) { |
| 159 | array_push(self::$fonts[$font_name]['specs']['ital'], $font_unit); |
| 160 | } |
| 161 | } |
| 162 | break; |
| 163 | case 'ital,wght': |
| 164 | foreach ($font_units as $font_unit) { |
| 165 | if (!in_array($font_unit, self::$fonts[$font_name]['specs']['ital,wght'])) { |
| 166 | array_push(self::$fonts[$font_name]['specs']['ital,wght'], $font_unit); |
| 167 | } |
| 168 | } |
| 169 | break; |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | /** |
| 176 | * Converts google font api version 1 font specification into API V2 |
| 177 | */ |
| 178 | private static function convert_v1_font_specs_to_v2() { |
| 179 | foreach (self::$fonts as $font_name => $font_details) { |
| 180 | if ('V2' == $font_details['version']) continue; |
| 181 | if (0 == count($font_details['specs'])) { |
| 182 | self::$fonts[$font_name]['specs'] = array( |
| 183 | 'wght' => array(), |
| 184 | 'ital' => array(), |
| 185 | 'ital,wght' => array(), |
| 186 | ); |
| 187 | } else { |
| 188 | foreach ($font_details['specs'] as $key => $detail) { |
| 189 | if (is_array($detail)) $detail = implode('', $detail); |
| 190 | switch ($detail) { |
| 191 | case 'i': |
| 192 | unset(self::$fonts[$font_name]['specs'][$key]); |
| 193 | self::$fonts[$font_name]['specs']['ital'] = array(1); |
| 194 | break; |
| 195 | case 'b': |
| 196 | unset(self::$fonts[$font_name]['specs'][$key]); |
| 197 | self::$fonts[$font_name]['specs']['wght'] = array(); |
| 198 | break; |
| 199 | case 'bi': |
| 200 | unset(self::$fonts[$font_name]['specs'][$key]); |
| 201 | self::$fonts[$font_name]['specs']['ital'] = array('0;1'); |
| 202 | break; |
| 203 | default: |
| 204 | unset(self::$fonts[$font_name]['specs'][$key]); |
| 205 | if (!isset(self::$fonts[$font_name]['specs']['ital,wght'])) { |
| 206 | self::$fonts[$font_name]['specs']['ital,wght'] = array(); |
| 207 | } |
| 208 | if ('inherit' === $detail) { |
| 209 | array_push(self::$fonts[$font_name]['specs']['ital,wght'], '0,400'); |
| 210 | array_push(self::$fonts[$font_name]['specs']['ital,wght'], '1,400'); |
| 211 | } elseif ('regular' === $detail) { |
| 212 | $detail = 'regular' === $detail ? 400 : $detail; |
| 213 | array_push(self::$fonts[$font_name]['specs']['ital,wght'], '0,' . $detail); |
| 214 | } elseif (false !== strpos($detail, 'i')) { |
| 215 | $detail = str_replace(array('italic', 'i'), '', $detail); |
| 216 | $detail = '' === $detail ? 400 : $detail; |
| 217 | array_push(self::$fonts[$font_name]['specs']['ital,wght'], '1,' . $detail); |
| 218 | } else { |
| 219 | $detail = str_replace(array('normal'), '', $detail); |
| 220 | $detail = '' === $detail ? 400 : $detail; |
| 221 | array_push(self::$fonts[$font_name]['specs']['ital,wght'], '0,' . $detail); |
| 222 | } |
| 223 | break; |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | /** |
| 231 | * Build valid Google font api 2 url string |
| 232 | * |
| 233 | * @return string $result Url string |
| 234 | */ |
| 235 | private static function build() { |
| 236 | $result = ''; |
| 237 | foreach (self::$fonts as $font_name => $font_details) { |
| 238 | if ('display=swap' == $font_name) continue; |
| 239 | if ('' != $result) { |
| 240 | $result .= '&'; |
| 241 | } |
| 242 | $result .= 'family=' . str_replace(' ', '+', $font_name); |
| 243 | $result .= self::specs_to_string($font_details['specs']); |
| 244 | } |
| 245 | return $result; |
| 246 | } |
| 247 | |
| 248 | /** |
| 249 | * Converts font specifications into a valid google font api2 url string |
| 250 | * |
| 251 | * @param array $font_specs Font style and weight specifications |
| 252 | * |
| 253 | * @return string |
| 254 | */ |
| 255 | private static function specs_to_string($font_specs) { |
| 256 | $result = array(); |
| 257 | $weights = isset($font_specs['wght']) && count($font_specs['wght']); |
| 258 | $italic_weights = isset($font_specs['ital']) && count($font_specs['ital']); |
| 259 | $all_weights = isset($font_specs['ital,wght']) && count($font_specs['ital,wght']); |
| 260 | |
| 261 | // Nothing is set, return |
| 262 | if (!$weights && !$italic_weights && !$all_weights) { |
| 263 | return ''; |
| 264 | } |
| 265 | |
| 266 | // Italic only |
| 267 | if ($italic_weights && !$weights && !$all_weights) { |
| 268 | if ('1' == $font_specs['ital'][0]) { |
| 269 | return ':ital@1'; |
| 270 | } elseif ('0;1' == $font_specs['ital'][0]) { |
| 271 | return ':ital@0;1'; |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | foreach ($font_specs as $style => $units) { |
| 276 | switch ($style) { |
| 277 | case 'wght': |
| 278 | foreach ($units as $unit) { |
| 279 | $multiple_units = explode(',', $unit); |
| 280 | if (count($multiple_units) > 0) { |
| 281 | foreach ($multiple_units as $single_unit) { |
| 282 | array_push($result, '0,' . $single_unit); |
| 283 | } |
| 284 | } else { |
| 285 | array_push($result, '0,' . $unit); |
| 286 | } |
| 287 | } |
| 288 | break; |
| 289 | case 'ital': |
| 290 | foreach ($units as $unit) { |
| 291 | array_push($result, 1 == $unit ? '1,400' : $unit); |
| 292 | } |
| 293 | break; |
| 294 | case 'ital,wght': |
| 295 | foreach ($units as $unit) { |
| 296 | array_push($result, $unit); |
| 297 | } |
| 298 | break; |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | sort($result); |
| 303 | return ':ital,wght@' . implode(';', array_unique($result)); |
| 304 | } |
| 305 | } |
| 306 |