Completed
Pull Request — develop (#1893)
by Aristeides
04:21
created

Kirki_Fonts_Google_Local::get_fonts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 4
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 5
rs 9.4285
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     http://opensource.org/licenses/https://opensource.org/licenses/MIT
12
 * @since       3.0.28
13
 */
14
15
16
// Do not allow directly accessing this file.
17
if ( ! defined( 'ABSPATH' ) ) {
18
	exit( 'Direct script access denied.' );
19
}
20
21
/**
22
 * The Kirki_Fonts object.
23
 *
24
 * @since 3.0.28
25
 */
26
final class Kirki_Fonts_Google_Local {
27
28
	/**
29
	 * The name of the font-family
30
	 *
31
	 * @access private
32
	 * @since 3.0.28
33
	 * @var string
34
	 */
35
	private $family;
36
37
	/**
38
	 * The system path where font-files are stored.
39
	 *
40
	 * @access private
41
	 * @since 3.0.28
42
	 * @var string
43
	 */
44
	private $folder_path;
45
46
	/**
47
	 * The URL where files for this font can be found.
48
	 *
49
	 * @access private
50
	 * @since 3.0.28
51
	 * @var string
52
	 */
53
	private $folder_url;
54
55
	/**
56
	 * The font-family array from the google-fonts API.
57
	 *
58
	 * @access private
59
	 * @since 3.0.28
60
	 * @var array
61
	 */
62
	private $font;
0 ignored issues
show
introduced by
The private property $font is not used, and could be removed.
Loading history...
63
64
	/**
65
	 * An array of instances for this object.
66
	 *
67
	 * @static
68
	 * @access private
69
	 * @since 3.0.28
70
	 * @var array
71
	 */
72
	private static $instances = array();
73
74
	/**
75
	 * Create an instance of this object for a specific font-family.
76
	 *
77
	 * @static
78
	 * @access public
79
	 * @since 3.0.28
80
	 * @param string $family The font-family name.
81
	 * @return Kirki_Fonts_Google_Local
82
	 */
83
	public static function do( $family ) {
84
		$key = sanitize_key( $family );
85
		if ( ! isset( self::$instances[ $key ] ) ) {
86
			self::$instances[ $key ] = new self( $family );
87
		}
88
		return self::$instances[ $key ];
89
	}
90
91
	/**
92
	 * Constructor.
93
	 *
94
	 * @access private
95
	 * @since 3.0.28
96
	 * @param string $family The font-family name.
97
	 */
98
	private function __construct( $family ) {
99
		$this->family      = $family;
100
		$key               = sanitize_key( $this->family );
101
		$this->folder_path = $this->get_root_path() . "/$key";
102
		$this->folder_url  = $this->get_root_url() . "/$key";
103
		$this->files        = $this->get_font_family();
0 ignored issues
show
Bug Best Practice introduced by
The property files does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
104
	}
105
106
	/**
107
	 * Gets the @font-face CSS.
108
	 *
109
	 * @access public
110
	 * @since 3.0.28
111
	 * @param array $variants The variants we want to get.
112
	 * @return string
113
	 */
114
	public function get_css( $variants = array() ) {
115
		if ( ! $this->files ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
116
			return;
117
		}
118
		$css = '';
119
120
		// If $variants is empty then use all variants available.
121
		if ( empty( $variants ) ) {
122
			$variants = array_keys( $this->files );
123
		}
124
125
		// Download files.
126
		$this->download_font_family( $variants );
127
128
		// Create the @font-face CSS.
129
		foreach ( $variants as $variant ) {
130
			$css .= $this->get_variant_fontface_css( $variant );
131
		}
132
		return $css;
133
	}
134
135
	/**
136
	 * Get the @font-face CSS for a specific variant.
137
	 *
138
	 * @access public
139
	 * @since 3.0.28
140
	 * @param string $variant The variant.
141
	 * @return string
142
	 */
143
	public function get_variant_fontface_css( $variant ) {
144
		$font_face = "@font-face{font-family:'{$this->family}';";
145
146
		// Get the font-style.
147
		$font_style = ( false !== strpos( $variant, 'italic' ) ) ? 'italic' : 'normal';
148
		$font_face .= "font-style:{$font_style};";
149
150
		// Get the font-weight.
151
		$font_weight = '400';
0 ignored issues
show
Unused Code introduced by
The assignment to $font_weight is dead and can be removed.
Loading history...
152
		$font_weight = str_replace( 'italic', '', $variant );
153
		$font_weight = ( ! $font_weight || 'regular' === $font_weight ) ? '400' : $font_weight;
154
		$font_face  .= "font-weight:{$font_weight};";
155
156
		// Get the font-names.
157
		$font_name_0 = $this->get_local_font_name( $variant, false );
158
		$font_name_1 = $this->get_local_font_name( $variant, true );
159
		$font_face  .= "src:local('{$font_name_0}'),";
160
		if ( $font_name_0 !== $font_name_1 ) {
161
			$font_face .= "local('{$font_name_1}'),";
162
		}
163
164
		// Get the font-url.
165
		$font_url = $this->get_variant_local_url( $variant );
166
167
		// Get the font-format.
168
		$font_format = ( strpos( $font_url, '.woff2' ) ) ? 'woff2' : 'truetype';
169
		$font_format = ( strpos( $font_url, '.woff' ) && ! strpos( $font_url, '.woff2' ) ) ? 'woff' : $font_format;
170
		$font_face  .= "url({$font_url}) format('{$font_format}');}";
171
172
		return $font_face;
173
	}
174
175
	/**
176
	 * Gets the local URL for a variant.
177
	 *
178
	 * @access public
179
	 * @since 3.0.28
180
	 * @param string $variant The variant.
181
	 * @return string         The URL.
182
	 */
183
	public function get_variant_local_url( $variant ) {
184
		$local_urls = $this->get_font_files_urls_local();
185
186
		if ( empty( $local_urls ) ) {
187
			return;
188
		}
189
190
		// Return the specific variant if we can find it.
191
		if ( isset( $local_urls[ $variant ] ) ) {
192
			return $local_urls[ $variant ];
193
		}
194
195
		// Return regular if the one we want could not be found.
196
		if ( isset( $local_urls['regular'] ) ) {
197
			return $local_urls['regular'];
198
		}
199
200
		// Return the first available if all else failed.
201
		$vals = array_values( $local_urls );
202
		return $vals[0];
203
	}
204
205
	/**
206
	 * Get the name of the font-family.
207
	 * This is used by @font-face in case the user already has the font downloaded locally.
208
	 *
209
	 * @access public
210
	 * @since 3.0.28
211
	 * @param string $variant The variant.
212
	 * @param bool   $compact Whether we want the compact formatting or not.
213
	 * @return string
214
	 */
215
	public function get_local_font_name( $variant, $compact = false ) {
216
		$variant_names = array(
217
			'100'       => 'Thin',
218
			'100i'      => 'Thin Italic',
219
			'100italic' => 'Thin Italic',
220
			'200'       => 'Extra-Light',
221
			'200i'      => 'Extra-Light Italic',
222
			'200italic' => 'Extra-Light Italic',
223
			'300'       => 'Light',
224
			'300i'      => 'Light Italic',
225
			'300italic' => 'Light Italic',
226
			'400'       => 'Regular',
227
			'regular'   => 'Regular',
228
			'400i'      => 'Regular Italic',
229
			'italic'    => 'Italic',
230
			'400italic' => 'Regular Italic',
231
			'500'       => 'Medium',
232
			'500i'      => 'Medium Italic',
233
			'500italic' => 'Medium Italic',
234
			'600'       => 'Semi-Bold',
235
			'600i'      => 'Semi-Bold Italic',
236
			'600italic' => 'Semi-Bold Italic',
237
			'700'       => 'Bold',
238
			'700i'      => 'Bold Italic',
239
			'700italic' => 'Bold Italic',
240
			'800'       => 'Extra-Bold',
241
			'800i'      => 'Extra-Bold Italic',
242
			'800italic' => 'Extra-Bold Italic',
243
			'900'       => 'Black',
244
			'900i'      => 'Black Italic',
245
			'900italic' => 'Black Italic',
246
		);
247
248
		$variant = (string) $variant;
249
		if ( $compact ) {
250
			if ( isset( $variant_names[ $variant ] ) ) {
251
				return str_replace( array( ' ', '-' ), '', $this->family ) . '-' . str_replace( array( ' ', '-' ), '', $variant_names[ $variant ] );
252
			}
253
			return str_replace( array( ' ', '-' ), '', $this->family );
254
		}
255
256
		if ( isset( $variant_names[ $variant ] ) ) {
257
			return $this->family . ' ' . $variant_names[ $variant ];
258
		}
259
		return $this->family;
260
	}
261
262
	/**
263
	 * Get an array of font-files.
264
	 * Only contains the filenames.
265
	 *
266
	 * @access public
267
	 * @since 3.0.28
268
	 * @return array
269
	 */
270
	public function get_font_files() {
271
		$files = array();
272
		foreach ( $this->files as $key => $url ) {
273
			$files[ $key ] = $this->get_filename_from_url( $url );
274
		}
275
		return $files;
276
	}
277
278
	/**
279
	 * Get an array of local file URLs.
280
	 *
281
	 * @access public
282
	 * @since 3.0.28
283
	 * @return array
284
	 */
285
	public function get_font_files_urls_local() {
286
		$urls  = array();
287
		$files = $this->get_font_files();
288
		foreach ( $files as $key => $file ) {
289
			$urls[ $key ] = $this->folder_url . '/' . $file;
290
		}
291
		return $urls;
292
	}
293
294
	/**
295
	 * Get an array of local file paths.
296
	 *
297
	 * @access public
298
	 * @since 3.0.28
299
	 * @return array
300
	 */
301
	public function get_font_files_paths() {
302
		$paths = array();
303
		$files = $this->get_font_files();
304
		foreach ( $files as $key => $file ) {
305
			$paths[ $key ] = $this->folder_path . '/' . $file;
306
		}
307
		return $paths;
308
	}
309
310
	/**
311
	 * Downloads a font-file and saves it locally.
312
	 *
313
	 * @access private
314
	 * @since 3.0.28
315
	 * @param string $url The URL of the file we want to get.
316
	 * @return bool
317
	 */
318
	private function download_font_file( $url ) {
319
		$contents = $this->get_remote_url_contents( $url );
320
		$path     = $this->folder_path . '/' . $this->get_filename_from_url( $url );
321
322
		// If the folder doesn't exist, create it.
323
		if ( ! file_exists( $this->folder_path ) ) {
324
			Kirki_Helper::init_filesystem()->mkdir( $this->folder_path, FS_CHMOD_FILE );
0 ignored issues
show
Bug introduced by
The constant FS_CHMOD_FILE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
325
		}
326
		// If the file exists no reason to do anything.
327
		if ( file_exists( $path ) ) {
328
			return true;
329
		}
330
331
		// Write file.
332
		return Kirki_Helper::init_filesystem()->put_contents( $path, $contents, FS_CHMOD_FILE );
333
	}
334
335
	/**
336
	 * Get a font-family from the array of google-fonts.
337
	 *
338
	 * @access public
339
	 * @since 3.0.28
340
	 * @return array
341
	 */
342
	public function get_font_family() {
343
344
		// Get the fonts array.
345
		$fonts = $this->get_fonts();
346
		if ( isset( $fonts[ $this->family ] ) ) {
347
			return $fonts[ $this->family ];
348
		}
349
		return array();
350
	}
351
352
	/**
353
	 * Gets the filename by breaking-down the URL parts.
354
	 *
355
	 * @access private
356
	 * @since 3.0.28
357
	 * @param string $url The URL.
358
	 * @return string     The filename.
359
	 */
360
	private function get_filename_from_url( $url ) {
361
		$url_parts   = explode( '/', $url );
362
		$parts_count = count( $url_parts );
363
		if ( 1 < $parts_count ) {
364
			return $url_parts[ count( $url_parts ) - 1 ];
365
		}
366
		return $url;
367
	}
368
369
	/**
370
	 * Get the font defined in the google-fonts API.
371
	 *
372
	 * @access private
373
	 * @since 3.0.28
374
	 * @return array
375
	 */
376
	private function get_fonts() {
377
		ob_start();
378
		include wp_normalize_path( dirname( __FILE__ ) . '/webfont-files.json' );
379
		$json = ob_get_clean();
380
		return json_decode( $json, true );
381
	}
382
383
	/**
384
	 * Gets the root fonts folder path.
385
	 * Other paths are built based on this.
386
	 *
387
	 * @since 1.5
388
	 * @access public
389
	 * @return string
390
	 */
391
	public function get_root_path() {
392
		// Get the upload directory for this site.
393
		$upload_dir = wp_upload_dir();
394
		$path       = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/webfonts';
395
396
		// If the folder doesn't exist, create it.
397
		if ( ! file_exists( $path ) ) {
398
			Kirki_Helper::init_filesystem()->mkdir( $path, FS_CHMOD_FILE );
0 ignored issues
show
Bug introduced by
The constant FS_CHMOD_FILE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
399
		}
400
401
		// Return the path.
402
		return apply_filters( 'kirki_googlefonts_root_path', $path );
403
	}
404
405
	/**
406
	 * Gets the root folder url.
407
	 * Other urls are built based on this.
408
	 *
409
	 * @since 1.5
410
	 * @access public
411
	 * @return string
412
	 */
413
	public function get_root_url() {
414
415
		// Get the upload directory for this site.
416
		$upload_dir = wp_upload_dir();
417
418
		// The URL.
419
		$url = trailingslashit( $upload_dir['baseurl'] );
420
		// Take care of domain mapping.
421
		// When using domain mapping we have to make sure that the URL to the file
422
		// does not include the original domain but instead the mapped domain.
423
		if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
0 ignored issues
show
Bug introduced by
The constant DOMAIN_MAPPING was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
424
			if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
425
				$mapped_domain   = domain_mapping_siteurl( false );
426
				$original_domain = get_original_url( 'siteurl' );
427
				$url = str_replace( $original_domain, $mapped_domain, $url );
428
			}
429
		}
430
		return apply_filters( 'kirki_googlefonts_root_url', untrailingslashit( esc_url_raw( $url ) ) . '/webfonts' );
431
	}
432
433
	/**
434
	 * Download font-family files.
435
	 *
436
	 * @access public
437
	 * @since 3.0.28
438
	 * @param array $variants An array of variants to download. Leave empty to download all.
439
	 * @return void
440
	 */
441
	public function download_font_family( $variants = array() ) {
442
		if ( empty( $variants ) ) {
443
			$variants = array_keys( $this->files );
444
		}
445
		foreach ( $this->files as $variant => $file ) {
446
			if ( in_array( $variant, $variants ) ) {
447
				$this->download_font_file( $file );
448
			}
449
		}
450
	}
451
452
	/**
453
	 * Gets the remote URL contents.
454
	 *
455
	 * @access private
456
	 * @since 3.0.28
457
	 * @param string $url The URL we want to get.
458
	 * @return string     The contents of the remote URL.
459
	 */
460
	public function get_remote_url_contents( $url ) {
461
		$response = wp_remote_get( $url );
462
		if ( is_wp_error( $response ) ) {
463
			return array();
464
		}
465
		$html = wp_remote_retrieve_body( $response );
466
		if ( is_wp_error( $html ) ) {
467
			return;
468
		}
469
		return $html;
470
	}
471
}
472