1 | <?php |
||
2 | /** |
||
3 | * Handles downloading a font from the google-fonts API locally. |
||
4 | * Solves privacy concerns with Google's CDN |
||
5 | * and their sometimes less-than-transparent policies. |
||
6 | * |
||
7 | * @package Kirki |
||
8 | * @category Core |
||
9 | * @author Aristeides Stathopoulos |
||
10 | * @copyright Copyright (c) 2017, Aristeides Stathopoulos |
||
11 | * @license https://opensource.org/licenses/MIT |
||
12 | * @since 3.0.28 |
||
13 | */ |
||
14 | |||
15 | // Do not allow directly accessing this file. |
||
16 | if ( ! defined( 'ABSPATH' ) ) { |
||
17 | exit( 'Direct script access denied.' ); |
||
18 | } |
||
19 | |||
20 | /** |
||
21 | * The Kirki_Fonts object. |
||
22 | * |
||
23 | * @since 3.0.28 |
||
24 | */ |
||
25 | final class Kirki_Fonts_Google_Local { |
||
26 | |||
27 | /** |
||
28 | * The name of the font-family |
||
29 | * |
||
30 | * @access private |
||
31 | * @since 3.0.28 |
||
32 | * @var string |
||
33 | */ |
||
34 | private $family; |
||
35 | |||
36 | /** |
||
37 | * The system path where font-files are stored. |
||
38 | * |
||
39 | * @access private |
||
40 | * @since 3.0.28 |
||
41 | * @var string |
||
42 | */ |
||
43 | private $folder_path; |
||
44 | |||
45 | /** |
||
46 | * The URL where files for this font can be found. |
||
47 | * |
||
48 | * @access private |
||
49 | * @since 3.0.28 |
||
50 | * @var string |
||
51 | */ |
||
52 | private $folder_url; |
||
53 | |||
54 | /** |
||
55 | * The font-family array from the google-fonts API. |
||
56 | * |
||
57 | * @access private |
||
58 | * @since 3.0.28 |
||
59 | * @var array |
||
60 | */ |
||
61 | private $font; |
||
62 | |||
63 | /** |
||
64 | * An array of instances for this object. |
||
65 | * |
||
66 | * @static |
||
67 | * @access private |
||
68 | * @since 3.0.28 |
||
69 | * @var array |
||
70 | */ |
||
71 | private static $instances = array(); |
||
72 | |||
73 | /** |
||
74 | * Create an instance of this object for a specific font-family. |
||
75 | * |
||
76 | * @static |
||
77 | * @access public |
||
78 | * @since 3.0.28 |
||
79 | * @param string $family The font-family name. |
||
80 | * @return Kirki_Fonts_Google_Local |
||
81 | */ |
||
82 | public static function init( $family ) { |
||
83 | $key = sanitize_key( $family ); |
||
84 | if ( ! isset( self::$instances[ $key ] ) ) { |
||
85 | self::$instances[ $key ] = new self( $family ); |
||
86 | } |
||
87 | return self::$instances[ $key ]; |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * Constructor. |
||
92 | * |
||
93 | * @access private |
||
94 | * @since 3.0.28 |
||
95 | * @param string $family The font-family name. |
||
96 | */ |
||
97 | private function __construct( $family ) { |
||
98 | $this->family = $family; |
||
99 | $key = sanitize_key( $this->family ); |
||
100 | $this->folder_path = $this->get_root_path() . "/$key"; |
||
101 | $this->folder_url = $this->get_root_url() . "/$key"; |
||
102 | $this->files = $this->get_font_family(); |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * Gets the @font-face CSS. |
||
107 | * |
||
108 | * @access public |
||
109 | * @since 3.0.28 |
||
110 | * @param array $variants The variants we want to get. |
||
111 | * @return string |
||
112 | */ |
||
113 | public function get_css( $variants = array() ) { |
||
114 | if ( ! $this->files ) { |
||
0 ignored issues
–
show
|
|||
115 | return; |
||
116 | } |
||
117 | $key = md5( wp_json_encode( $this->files ) ); |
||
118 | $cached = get_transient( $key ); |
||
119 | if ( $cached ) { |
||
120 | return $cached; |
||
121 | } |
||
122 | $css = ''; |
||
123 | |||
124 | // If $variants is empty then use all variants available. |
||
125 | if ( empty( $variants ) ) { |
||
126 | $variants = array_keys( $this->files ); |
||
127 | } |
||
128 | |||
129 | // Download files. |
||
130 | $this->download_font_family( $variants ); |
||
131 | |||
132 | // Create the @font-face CSS. |
||
133 | foreach ( $variants as $variant ) { |
||
134 | $css .= $this->get_variant_fontface_css( $variant ); |
||
135 | } |
||
136 | set_transient( $key, $css, DAY_IN_SECONDS ); |
||
137 | return $css; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Get the @font-face CSS for a specific variant. |
||
142 | * |
||
143 | * @access public |
||
144 | * @since 3.0.28 |
||
145 | * @param string $variant The variant. |
||
146 | * @return string |
||
147 | */ |
||
148 | public function get_variant_fontface_css( $variant ) { |
||
149 | $font_face = "@font-face{font-family:'{$this->family}';"; |
||
150 | |||
151 | // Get the font-style. |
||
152 | $font_style = ( false !== strpos( $variant, 'italic' ) ) ? 'italic' : 'normal'; |
||
153 | $font_face .= "font-style:{$font_style};"; |
||
154 | |||
155 | // Get the font-weight. |
||
156 | $font_weight = '400'; |
||
0 ignored issues
–
show
|
|||
157 | $font_weight = str_replace( 'italic', '', $variant ); |
||
158 | $font_weight = ( ! $font_weight || 'regular' === $font_weight ) ? '400' : $font_weight; |
||
159 | $font_face .= "font-weight:{$font_weight};"; |
||
160 | |||
161 | // Get the font-names. |
||
162 | $font_name_0 = $this->get_local_font_name( $variant, false ); |
||
163 | $font_name_1 = $this->get_local_font_name( $variant, true ); |
||
164 | $font_face .= "src:local('{$font_name_0}'),"; |
||
165 | if ( $font_name_0 !== $font_name_1 ) { |
||
166 | $font_face .= "local('{$font_name_1}'),"; |
||
167 | } |
||
168 | |||
169 | // Get the font-url. |
||
170 | $font_url = $this->get_variant_local_url( $variant ); |
||
171 | $paths = $this->get_font_files_paths(); |
||
172 | if ( ! file_exists( $paths[ $variant ] ) ) { |
||
173 | $font_url = $this->files[ $variant ]; |
||
174 | } |
||
175 | |||
176 | // Get the font-format. |
||
177 | $font_format = ( strpos( $font_url, '.woff2' ) ) ? 'woff2' : 'truetype'; |
||
178 | $font_format = ( strpos( $font_url, '.woff' ) && ! strpos( $font_url, '.woff2' ) ) ? 'woff' : $font_format; |
||
179 | $font_face .= "url({$font_url}) format('{$font_format}');}"; |
||
180 | |||
181 | return $font_face; |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Gets the local URL for a variant. |
||
186 | * |
||
187 | * @access public |
||
188 | * @since 3.0.28 |
||
189 | * @param string $variant The variant. |
||
190 | * @return string The URL. |
||
191 | */ |
||
192 | public function get_variant_local_url( $variant ) { |
||
193 | $local_urls = $this->get_font_files_urls_local(); |
||
194 | |||
195 | if ( empty( $local_urls ) ) { |
||
196 | return; |
||
197 | } |
||
198 | |||
199 | // Return the specific variant if we can find it. |
||
200 | if ( isset( $local_urls[ $variant ] ) ) { |
||
201 | return $local_urls[ $variant ]; |
||
202 | } |
||
203 | |||
204 | // Return regular if the one we want could not be found. |
||
205 | if ( isset( $local_urls['regular'] ) ) { |
||
206 | return $local_urls['regular']; |
||
207 | } |
||
208 | |||
209 | // Return the first available if all else failed. |
||
210 | $vals = array_values( $local_urls ); |
||
211 | return $vals[0]; |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * Get the name of the font-family. |
||
216 | * This is used by @font-face in case the user already has the font downloaded locally. |
||
217 | * |
||
218 | * @access public |
||
219 | * @since 3.0.28 |
||
220 | * @param string $variant The variant. |
||
221 | * @param bool $compact Whether we want the compact formatting or not. |
||
222 | * @return string |
||
223 | */ |
||
224 | public function get_local_font_name( $variant, $compact = false ) { |
||
225 | $variant_names = array( |
||
226 | '100' => 'Thin', |
||
227 | '100i' => 'Thin Italic', |
||
228 | '100italic' => 'Thin Italic', |
||
229 | '200' => 'Extra-Light', |
||
230 | '200i' => 'Extra-Light Italic', |
||
231 | '200italic' => 'Extra-Light Italic', |
||
232 | '300' => 'Light', |
||
233 | '300i' => 'Light Italic', |
||
234 | '300italic' => 'Light Italic', |
||
235 | '400' => 'Regular', |
||
236 | 'regular' => 'Regular', |
||
237 | '400i' => 'Regular Italic', |
||
238 | 'italic' => 'Italic', |
||
239 | '400italic' => 'Regular Italic', |
||
240 | '500' => 'Medium', |
||
241 | '500i' => 'Medium Italic', |
||
242 | '500italic' => 'Medium Italic', |
||
243 | '600' => 'Semi-Bold', |
||
244 | '600i' => 'Semi-Bold Italic', |
||
245 | '600italic' => 'Semi-Bold Italic', |
||
246 | '700' => 'Bold', |
||
247 | '700i' => 'Bold Italic', |
||
248 | '700italic' => 'Bold Italic', |
||
249 | '800' => 'Extra-Bold', |
||
250 | '800i' => 'Extra-Bold Italic', |
||
251 | '800italic' => 'Extra-Bold Italic', |
||
252 | '900' => 'Black', |
||
253 | '900i' => 'Black Italic', |
||
254 | '900italic' => 'Black Italic', |
||
255 | ); |
||
256 | |||
257 | $variant = (string) $variant; |
||
258 | if ( $compact ) { |
||
259 | if ( isset( $variant_names[ $variant ] ) ) { |
||
260 | return str_replace( array( ' ', '-' ), '', $this->family ) . '-' . str_replace( array( ' ', '-' ), '', $variant_names[ $variant ] ); |
||
261 | } |
||
262 | return str_replace( array( ' ', '-' ), '', $this->family ); |
||
263 | } |
||
264 | |||
265 | if ( isset( $variant_names[ $variant ] ) ) { |
||
266 | return $this->family . ' ' . $variant_names[ $variant ]; |
||
267 | } |
||
268 | return $this->family; |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Get an array of font-files. |
||
273 | * Only contains the filenames. |
||
274 | * |
||
275 | * @access public |
||
276 | * @since 3.0.28 |
||
277 | * @return array |
||
278 | */ |
||
279 | public function get_font_files() { |
||
280 | $files = array(); |
||
281 | foreach ( $this->files as $key => $url ) { |
||
282 | $files[ $key ] = $this->get_filename_from_url( $url ); |
||
283 | } |
||
284 | return $files; |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * Get an array of local file URLs. |
||
289 | * |
||
290 | * @access public |
||
291 | * @since 3.0.28 |
||
292 | * @return array |
||
293 | */ |
||
294 | public function get_font_files_urls_local() { |
||
295 | $urls = array(); |
||
296 | $files = $this->get_font_files(); |
||
297 | foreach ( $files as $key => $file ) { |
||
298 | $urls[ $key ] = $this->folder_url . '/' . $file; |
||
299 | } |
||
300 | return $urls; |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Get an array of local file paths. |
||
305 | * |
||
306 | * @access public |
||
307 | * @since 3.0.28 |
||
308 | * @return array |
||
309 | */ |
||
310 | public function get_font_files_paths() { |
||
311 | $paths = array(); |
||
312 | $files = $this->get_font_files(); |
||
313 | foreach ( $files as $key => $file ) { |
||
314 | $paths[ $key ] = $this->folder_path . '/' . $file; |
||
315 | } |
||
316 | return $paths; |
||
317 | } |
||
318 | |||
319 | /** |
||
320 | * Downloads a font-file and saves it locally. |
||
321 | * |
||
322 | * @access private |
||
323 | * @since 3.0.28 |
||
324 | * @param string $url The URL of the file we want to get. |
||
325 | * @return bool |
||
326 | */ |
||
327 | private function download_font_file( $url ) { |
||
328 | $contents = $this->get_remote_url_contents( $url ); |
||
329 | $path = $this->folder_path . '/' . $this->get_filename_from_url( $url ); |
||
330 | |||
331 | // If the folder doesn't exist, create it. |
||
332 | if ( ! file_exists( $this->folder_path ) ) { |
||
333 | wp_mkdir_p( $this->folder_path ); |
||
334 | } |
||
335 | // If the file exists no reason to do anything. |
||
336 | if ( file_exists( $path ) ) { |
||
337 | return true; |
||
338 | } |
||
339 | |||
340 | // Write file. |
||
341 | return Kirki_Helper::init_filesystem()->put_contents( $path, $contents, FS_CHMOD_FILE ); |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * Get a font-family from the array of google-fonts. |
||
346 | * |
||
347 | * @access public |
||
348 | * @since 3.0.28 |
||
349 | * @return array |
||
350 | */ |
||
351 | public function get_font_family() { |
||
352 | |||
353 | // Get the fonts array. |
||
354 | $fonts = $this->get_fonts(); |
||
355 | if ( isset( $fonts[ $this->family ] ) ) { |
||
356 | return $fonts[ $this->family ]; |
||
357 | } |
||
358 | return array(); |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Gets the filename by breaking-down the URL parts. |
||
363 | * |
||
364 | * @access private |
||
365 | * @since 3.0.28 |
||
366 | * @param string $url The URL. |
||
367 | * @return string The filename. |
||
368 | */ |
||
369 | private function get_filename_from_url( $url ) { |
||
370 | $url_parts = explode( '/', $url ); |
||
371 | $parts_count = count( $url_parts ); |
||
372 | if ( 1 < $parts_count ) { |
||
373 | return $url_parts[ count( $url_parts ) - 1 ]; |
||
374 | } |
||
375 | return $url; |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * Get the font defined in the google-fonts API. |
||
380 | * |
||
381 | * @access private |
||
382 | * @since 3.0.28 |
||
383 | * @return array |
||
384 | */ |
||
385 | private function get_fonts() { |
||
386 | ob_start(); |
||
387 | include wp_normalize_path( dirname( __FILE__ ) . '/webfont-files.json' ); |
||
388 | $json = ob_get_clean(); |
||
389 | return json_decode( $json, true ); |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Gets the root fonts folder path. |
||
394 | * Other paths are built based on this. |
||
395 | * |
||
396 | * @since 1.5 |
||
397 | * @access public |
||
398 | * @return string |
||
399 | */ |
||
400 | public function get_root_path() { |
||
401 | |||
402 | // Get the upload directory for this site. |
||
403 | $upload_dir = wp_upload_dir(); |
||
404 | $path = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/webfonts'; |
||
405 | |||
406 | // If the folder doesn't exist, create it. |
||
407 | if ( ! file_exists( $path ) ) { |
||
408 | wp_mkdir_p( $path ); |
||
409 | } |
||
410 | |||
411 | // Return the path. |
||
412 | return apply_filters( 'kirki_googlefonts_root_path', $path ); |
||
413 | } |
||
414 | |||
415 | /** |
||
416 | * Gets the root folder url. |
||
417 | * Other urls are built based on this. |
||
418 | * |
||
419 | * @since 1.5 |
||
420 | * @access public |
||
421 | * @return string |
||
422 | */ |
||
423 | public function get_root_url() { |
||
424 | |||
425 | // Get the upload directory for this site. |
||
426 | $upload_dir = wp_upload_dir(); |
||
427 | |||
428 | // The URL. |
||
429 | $url = trailingslashit( $upload_dir['baseurl'] ); |
||
430 | |||
431 | // Take care of domain mapping. |
||
432 | // When using domain mapping we have to make sure that the URL to the file |
||
433 | // does not include the original domain but instead the mapped domain. |
||
434 | if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) { |
||
435 | if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) { |
||
436 | $mapped_domain = domain_mapping_siteurl( false ); |
||
437 | $original_domain = get_original_url( 'siteurl' ); |
||
438 | $url = str_replace( $original_domain, $mapped_domain, $url ); |
||
439 | } |
||
440 | } |
||
441 | $url = str_replace( array( 'https://', 'http://' ), '//', $url ); |
||
442 | return apply_filters( 'kirki_googlefonts_root_url', untrailingslashit( esc_url_raw( $url ) ) . '/webfonts' ); |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * Download font-family files. |
||
447 | * |
||
448 | * @access public |
||
449 | * @since 3.0.28 |
||
450 | * @param array $variants An array of variants to download. Leave empty to download all. |
||
451 | * @return void |
||
452 | */ |
||
453 | public function download_font_family( $variants = array() ) { |
||
454 | if ( empty( $variants ) ) { |
||
455 | $variants = array_keys( $this->files ); |
||
456 | } |
||
457 | foreach ( $this->files as $variant => $file ) { |
||
458 | if ( in_array( $variant, $variants ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict |
||
459 | $this->download_font_file( $file ); |
||
460 | } |
||
461 | } |
||
462 | } |
||
463 | |||
464 | /** |
||
465 | * Gets the remote URL contents. |
||
466 | * |
||
467 | * @access private |
||
468 | * @since 3.0.28 |
||
469 | * @param string $url The URL we want to get. |
||
470 | * @return string The contents of the remote URL. |
||
471 | */ |
||
472 | public function get_remote_url_contents( $url ) { |
||
473 | $response = wp_remote_get( $url ); |
||
474 | if ( is_wp_error( $response ) ) { |
||
475 | return array(); |
||
476 | } |
||
477 | $html = wp_remote_retrieve_body( $response ); |
||
478 | if ( is_wp_error( $html ) ) { |
||
479 | return; |
||
480 | } |
||
481 | return $html; |
||
482 | } |
||
483 | } |
||
484 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.