PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.5.2
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.5.2
3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / includes / libraries / wptt-webfont-loader.php
everest-forms / includes / libraries Last commit date
wp-async-request.php 8 years ago wp-background-process.php 7 years ago wptt-webfont-loader.php 2 years ago
wptt-webfont-loader.php
712 lines
1 <?php
2 /**
3 * Download webfonts locally.
4 *
5 * @package wptt/font-loader
6 * @license https://opensource.org/licenses/MIT
7 */
8
9 if ( ! class_exists( 'WPTT_WebFont_Loader' ) ) {
10 /**
11 * Download webfonts locally.
12 */
13 class WPTT_WebFont_Loader {
14
15 /**
16 * The font-format.
17 *
18 * Use "woff" or "woff2".
19 * This will change the user-agent user to make the request.
20 *
21 * @access protected
22 * @since 1.0.0
23 * @var string
24 */
25 protected $font_format = 'woff2';
26
27 /**
28 * The remote URL.
29 *
30 * @access protected
31 * @since 1.1.0
32 * @var string
33 */
34 protected $remote_url;
35
36 /**
37 * Base path.
38 *
39 * @access protected
40 * @since 1.1.0
41 * @var string
42 */
43 protected $base_path;
44
45 /**
46 * Base URL.
47 *
48 * @access protected
49 * @since 1.1.0
50 * @var string
51 */
52 protected $base_url;
53
54 /**
55 * Subfolder name.
56 *
57 * @access protected
58 * @since 1.1.0
59 * @var string
60 */
61 protected $subfolder_name;
62
63 /**
64 * The fonts folder.
65 *
66 * @access protected
67 * @since 1.1.0
68 * @var string
69 */
70 protected $fonts_folder;
71
72 /**
73 * The local stylesheet's path.
74 *
75 * @access protected
76 * @since 1.1.0
77 * @var string
78 */
79 protected $local_stylesheet_path;
80
81 /**
82 * The local stylesheet's URL.
83 *
84 * @access protected
85 * @since 1.1.0
86 * @var string
87 */
88 protected $local_stylesheet_url;
89
90 /**
91 * The remote CSS.
92 *
93 * @access protected
94 * @since 1.1.0
95 * @var string
96 */
97 protected $remote_styles;
98
99 /**
100 * The final CSS.
101 *
102 * @access protected
103 * @since 1.1.0
104 * @var string
105 */
106 protected $css;
107
108 /**
109 * Cleanup routine frequency.
110 */
111 const CLEANUP_FREQUENCY = 'monthly';
112
113 /**
114 * Constructor.
115 *
116 * Get a new instance of the object for a new URL.
117 *
118 * @access public
119 * @since 1.1.0
120 * @param string $url The remote URL.
121 */
122 public function __construct( $url = '' ) {
123 $this->remote_url = $url;
124
125 // Add a cleanup routine.
126 $this->schedule_cleanup();
127 add_action( 'delete_fonts_folder', array( $this, 'delete_fonts_folder' ) );
128 }
129
130 /**
131 * Get the local URL which contains the styles.
132 *
133 * Fallback to the remote URL if we were unable to write the file locally.
134 *
135 * @access public
136 * @since 1.1.0
137 * @return string
138 */
139 public function get_url() {
140
141 // Check if the local stylesheet exists.
142 if ( $this->local_file_exists() ) {
143
144 // Attempt to update the stylesheet. Return the local URL on success.
145 if ( $this->write_stylesheet() ) {
146 return $this->get_local_stylesheet_url();
147 }
148 }
149
150 // If the local file exists, return its URL, with a fallback to the remote URL.
151 return file_exists( $this->get_local_stylesheet_path() )
152 ? $this->get_local_stylesheet_url()
153 : $this->remote_url;
154 }
155
156 /**
157 * Get the local stylesheet URL.
158 *
159 * @access public
160 * @since 1.1.0
161 * @return string
162 */
163 public function get_local_stylesheet_url() {
164 if ( ! $this->local_stylesheet_url ) {
165 $this->local_stylesheet_url = str_replace(
166 $this->get_base_path(),
167 $this->get_base_url(),
168 $this->get_local_stylesheet_path()
169 );
170 }
171 return $this->local_stylesheet_url;
172 }
173
174 /**
175 * Get styles with fonts downloaded locally.
176 *
177 * @access public
178 * @since 1.0.0
179 * @return string
180 */
181 public function get_styles() {
182
183 // If we already have the local file, return its contents.
184 $local_stylesheet_contents = $this->get_local_stylesheet_contents();
185 if ( $local_stylesheet_contents ) {
186 return $local_stylesheet_contents;
187 }
188
189 // Get the remote URL contents.
190 $this->remote_styles = $this->get_remote_url_contents();
191
192 // Get an array of locally-hosted files.
193 $files = $this->get_local_files_from_css();
194
195 // Convert paths to URLs.
196 foreach ( $files as $remote => $local ) {
197 $files[ $remote ] = str_replace(
198 $this->get_base_path(),
199 $this->get_base_url(),
200 $local
201 );
202 }
203
204 $this->css = str_replace(
205 array_keys( $files ),
206 array_values( $files ),
207 $this->remote_styles
208 );
209
210 $this->write_stylesheet();
211
212 return $this->css;
213 }
214
215 /**
216 * Get local stylesheet contents.
217 *
218 * @access public
219 * @since 1.1.0
220 * @return string|false Returns the remote URL contents.
221 */
222 public function get_local_stylesheet_contents() {
223 $local_path = $this->get_local_stylesheet_path();
224
225 // Check if the local stylesheet exists.
226 if ( $this->local_file_exists() ) {
227
228 // Attempt to update the stylesheet. Return false on fail.
229 if ( ! $this->write_stylesheet() ) {
230 return false;
231 }
232 }
233
234 ob_start();
235 include $local_path;
236 return ob_get_clean();
237 }
238
239 /**
240 * Get remote file contents.
241 *
242 * @access public
243 * @since 1.0.0
244 * @return string Returns the remote URL contents.
245 */
246 public function get_remote_url_contents() {
247
248 /**
249 * The user-agent we want to use.
250 *
251 * The default user-agent is the only one compatible with woff (not woff2)
252 * which also supports unicode ranges.
253 */
254 $user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8';
255
256 // Switch to a user-agent supporting woff2 if we don't need to support IE.
257 if ( 'woff2' === $this->font_format ) {
258 $user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0';
259 }
260
261 // Get the response.
262 $response = wp_remote_get( $this->remote_url, array( 'user-agent' => $user_agent ) );
263
264 // Early exit if there was an error.
265 if ( is_wp_error( $response ) ) {
266 return '';
267 }
268
269 // Get the CSS from our response.
270 $contents = wp_remote_retrieve_body( $response );
271
272 return $contents;
273 }
274
275 /**
276 * Download files mentioned in our CSS locally.
277 *
278 * @access public
279 * @since 1.0.0
280 * @return array Returns an array of remote URLs and their local counterparts.
281 */
282 public function get_local_files_from_css() {
283 $font_files = $this->get_remote_files_from_css();
284 $stored = get_site_option( 'downloaded_font_files', array() );
285 $change = false; // If in the end this is true, we need to update the cache option.
286
287 if ( ! defined( 'FS_CHMOD_DIR' ) ) {
288 define( 'FS_CHMOD_DIR', ( 0755 & ~ umask() ) );
289 }
290
291 // If the fonts folder don't exist, create it.
292 if ( ! file_exists( $this->get_fonts_folder() ) ) {
293 $this->get_filesystem()->mkdir( $this->get_fonts_folder(), FS_CHMOD_DIR );
294 }
295
296 foreach ( $font_files as $font_family => $files ) {
297
298 // The folder path for this font-family.
299 $folder_path = $this->get_fonts_folder() . '/' . $font_family;
300
301 // If the folder doesn't exist, create it.
302 if ( ! file_exists( $folder_path ) ) {
303 $this->get_filesystem()->mkdir( $folder_path, FS_CHMOD_DIR );
304 }
305
306 foreach ( $files as $url ) {
307
308 // Get the filename.
309 $filename = basename( wp_parse_url( $url, PHP_URL_PATH ) );
310 $font_path = $folder_path . '/' . $filename;
311 /**
312 * In Typekit, the filename will always be the same. We also need to check for query vars in their URLs.
313 * They provide this font variation description that we can use https://github.com/typekit/fvd
314 */
315 $queries = parse_url( $url, PHP_URL_QUERY );
316 if ( ! empty( $queries ) ) {
317 $query_args = array();
318 parse_str( $queries, $query_args );
319 if ( array_key_exists( 'fvd', $query_args ) ) {
320 $font_path .= $query_args['fvd'];
321 }
322 }
323
324 // Check if the file already exists.
325 if ( file_exists( $font_path ) ) {
326
327 // Skip if already cached.
328 if ( isset( $stored[ $url ] ) ) {
329 continue;
330 }
331
332 // Add file to the cache and change the $changed var to indicate we need to update the option.
333 $stored[ $url ] = $font_path;
334 $change = true;
335
336 // Since the file exists we don't need to proceed with downloading it.
337 continue;
338 }
339
340 /**
341 * If we got this far, we need to download the file.
342 */
343
344 // require file.php if the download_url function doesn't exist.
345 if ( ! function_exists( 'download_url' ) ) {
346 require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' );
347 }
348
349 // Download file to temporary location.
350 $tmp_path = download_url( $url );
351
352 // Make sure there were no errors.
353 if ( is_wp_error( $tmp_path ) ) {
354 continue;
355 }
356
357 // Move temp file to final destination.
358 $success = $this->get_filesystem()->move( $tmp_path, $font_path, true );
359 if ( $success ) {
360 $stored[ $url ] = $font_path;
361 $change = true;
362 }
363 }
364 }
365
366 // If there were changes, update the option.
367 if ( $change ) {
368
369 // Cleanup the option and then save it.
370 foreach ( $stored as $url => $path ) {
371 if ( ! file_exists( $path ) ) {
372 unset( $stored[ $url ] );
373 }
374 }
375 update_site_option( 'downloaded_font_files', $stored );
376 }
377
378 return $stored;
379 }
380
381 /**
382 * Get font files from the CSS.
383 *
384 * @access public
385 * @since 1.0.0
386 * @return array Returns an array of font-families and the font-files used.
387 */
388 public function get_remote_files_from_css() {
389
390 $font_faces = explode( '@font-face', $this->remote_styles );
391
392 $result = array();
393
394 // Loop all our font-face declarations.
395 foreach ( $font_faces as $font_face ) {
396
397 // Make sure we only process styles inside this declaration.
398 $style = explode( '}', $font_face )[0];
399
400 // Sanity check.
401 if ( false === strpos( $style, 'font-family' ) ) {
402 continue;
403 }
404
405 // Get an array of our font-families.
406 preg_match_all( '/font-family.*?\;/', $style, $matched_font_families );
407
408 // Get an array of our font-files.
409 preg_match_all( '/url\(.*?\)/i', $style, $matched_font_files );
410
411 // Get the font-family name.
412 $font_family = 'unknown';
413 if ( isset( $matched_font_families[0] ) && isset( $matched_font_families[0][0] ) ) {
414 $font_family = rtrim( ltrim( $matched_font_families[0][0], 'font-family:' ), ';' );
415 $font_family = trim( str_replace( array( "'", ';' ), '', $font_family ) );
416 $font_family = sanitize_key( strtolower( str_replace( ' ', '-', $font_family ) ) );
417 }
418
419 // Make sure the font-family is set in our array.
420 if ( ! isset( $result[ $font_family ] ) ) {
421 $result[ $font_family ] = array();
422 }
423
424 // Get files for this font-family and add them to the array.
425 foreach ( $matched_font_files as $match ) {
426
427 // Sanity check.
428 if ( ! isset( $match[0] ) ) {
429 continue;
430 }
431
432 // Add the file URL.
433 $font_family_url = rtrim( ltrim( $match[0], 'url(' ), ')' );
434 $font_family_url = str_replace( '"', '', $font_family_url );
435
436 // Make sure to convert relative URLs to absolute.
437 $font_family_url = $this->get_absolute_path( $font_family_url );
438
439 $result[ $font_family ][] = $font_family_url;
440 }
441
442 // Make sure we have unique items.
443 // We're using array_flip here instead of array_unique for improved performance.
444 $result[ $font_family ] = array_flip( array_flip( $result[ $font_family ] ) );
445 }
446
447 return $result;
448 }
449
450 /**
451 * Write the CSS to the filesystem.
452 *
453 * @access protected
454 * @since 1.1.0
455 * @return string|false Returns the absolute path of the file on success, or false on fail.
456 */
457 protected function write_stylesheet() {
458 $file_path = $this->get_local_stylesheet_path();
459 $filesystem = $this->get_filesystem();
460
461 if ( ! defined( 'FS_CHMOD_DIR' ) ) {
462 define( 'FS_CHMOD_DIR', ( 0755 & ~ umask() ) );
463 }
464
465 // If the folder doesn't exist, create it.
466 if ( ! file_exists( $this->get_fonts_folder() ) ) {
467 $this->get_filesystem()->mkdir( $this->get_fonts_folder(), FS_CHMOD_DIR );
468 }
469
470 // If the file doesn't exist, create it. Return false if it can not be created.
471 if ( ! $filesystem->exists( $file_path ) && ! $filesystem->touch( $file_path ) ) {
472 return false;
473 }
474
475 // If we got this far, we need to write the file.
476 // Get the CSS.
477 if ( ! $this->css ) {
478 $this->get_styles();
479 }
480
481 // Put the contents in the file. Return false if that fails.
482 if ( ! $filesystem->put_contents( $file_path, $this->css ) ) {
483 return false;
484 }
485
486 return $file_path;
487 }
488
489 /**
490 * Get the stylesheet path.
491 *
492 * @access public
493 * @since 1.1.0
494 * @return string
495 */
496 public function get_local_stylesheet_path() {
497 if ( ! $this->local_stylesheet_path ) {
498 $this->local_stylesheet_path = $this->get_fonts_folder() . '/' . $this->get_local_stylesheet_filename() . '.css';
499 }
500 return $this->local_stylesheet_path;
501 }
502
503 /**
504 * Get the local stylesheet filename.
505 *
506 * This is a hash, generated from the site-URL, the wp-content path and the URL.
507 * This way we can avoid issues with sites changing their URL, or the wp-content path etc.
508 *
509 * @access public
510 * @since 1.1.0
511 * @return string
512 */
513 public function get_local_stylesheet_filename() {
514 return md5( $this->get_base_url() . $this->get_base_path() . $this->remote_url . $this->font_format );
515 }
516
517 /**
518 * Set the font-format to be used.
519 *
520 * @access public
521 * @since 1.0.0
522 * @param string $format The format to be used. Use "woff" or "woff2".
523 * @return void
524 */
525 public function set_font_format( $format = 'woff2' ) {
526 $this->font_format = $format;
527 }
528
529 /**
530 * Check if the local stylesheet exists.
531 *
532 * @access public
533 * @since 1.1.0
534 * @return bool
535 */
536 public function local_file_exists() {
537 return ( ! file_exists( $this->get_local_stylesheet_path() ) );
538 }
539
540 /**
541 * Get the base path.
542 *
543 * @access public
544 * @since 1.1.0
545 * @return string
546 */
547 public function get_base_path() {
548 if ( ! $this->base_path ) {
549 $this->base_path = apply_filters( 'wptt_get_local_fonts_base_path', $this->get_filesystem()->wp_content_dir() );
550 }
551 return $this->base_path;
552 }
553
554 /**
555 * Get the base URL.
556 *
557 * @access public
558 * @since 1.1.0
559 * @return string
560 */
561 public function get_base_url() {
562 if ( ! $this->base_url ) {
563 $this->base_url = apply_filters( 'wptt_get_local_fonts_base_url', content_url() );
564 }
565 return $this->base_url;
566 }
567
568 /**
569 * Get the subfolder name.
570 *
571 * @access public
572 * @since 1.1.0
573 * @return string
574 */
575 public function get_subfolder_name() {
576 if ( ! $this->subfolder_name ) {
577 $this->subfolder_name = apply_filters( 'wptt_get_local_fonts_subfolder_name', 'fonts' );
578 }
579 return $this->subfolder_name;
580 }
581
582 /**
583 * Get the folder for fonts.
584 *
585 * @access public
586 * @return string
587 */
588 public function get_fonts_folder() {
589 if ( ! $this->fonts_folder ) {
590 $this->fonts_folder = $this->get_base_path();
591 if ( $this->get_subfolder_name() ) {
592 $this->fonts_folder .= '/' . $this->get_subfolder_name();
593 }
594 }
595 return $this->fonts_folder;
596 }
597
598 /**
599 * Schedule a cleanup.
600 *
601 * Deletes the fonts files on a regular basis.
602 * This way font files will get updated regularly,
603 * and we avoid edge cases where unused files remain in the server.
604 *
605 * @access public
606 * @since 1.1.0
607 * @return void
608 */
609 public function schedule_cleanup() {
610 if ( ! is_multisite() || ( is_multisite() && is_main_site() ) ) {
611 if ( ! wp_next_scheduled( 'delete_fonts_folder' ) && ! wp_installing() ) {
612 wp_schedule_event( time(), self::CLEANUP_FREQUENCY, 'delete_fonts_folder' );
613 }
614 }
615 }
616
617 /**
618 * Delete the fonts folder.
619 *
620 * This runs as part of a cleanup routine.
621 *
622 * @access public
623 * @since 1.1.0
624 * @return bool
625 */
626 public function delete_fonts_folder() {
627 return $this->get_filesystem()->delete( $this->get_fonts_folder(), true );
628 }
629
630 /**
631 * Get the filesystem.
632 *
633 * @access protected
634 * @since 1.0.0
635 * @return \WP_Filesystem_Base
636 */
637 protected function get_filesystem() {
638 global $wp_filesystem;
639
640 // If the filesystem has not been instantiated yet, do it here.
641 if ( ! $wp_filesystem ) {
642 if ( ! function_exists( 'WP_Filesystem' ) ) {
643 require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' );
644 }
645 WP_Filesystem();
646 }
647 return $wp_filesystem;
648 }
649
650 /**
651 * Get an absolute URL from a relative URL.
652 *
653 * @access protected
654 *
655 * @param string $url The URL.
656 *
657 * @return string
658 */
659 protected function get_absolute_path( $url ) {
660
661 // If dealing with a root-relative URL.
662 if ( 0 === stripos( $url, '/' ) ) {
663 $parsed_url = parse_url( $this->remote_url );
664 return $parsed_url['scheme'] . '://' . $parsed_url['hostname'] . $url;
665 }
666
667 return $url;
668 }
669 }
670 }
671
672 if ( ! function_exists( 'wptt_get_webfont_styles' ) ) {
673 /**
674 * Get styles for a webfont.
675 *
676 * This will get the CSS from the remote API,
677 * download any fonts it contains,
678 * replace references to remote URLs with locally-downloaded assets,
679 * and finally return the resulting CSS.
680 *
681 * @since 1.0.0
682 *
683 * @param string $url The URL of the remote webfont.
684 * @param string $format The font-format. If you need to support IE, change this to "woff".
685 *
686 * @return string Returns the CSS.
687 */
688 function wptt_get_webfont_styles( $url, $format = 'woff2' ) {
689 $font = new WPTT_WebFont_Loader( $url );
690 $font->set_font_format( $format );
691 return $font->get_styles();
692 }
693 }
694
695 if ( ! function_exists( 'wptt_get_webfont_url' ) ) {
696 /**
697 * Get a stylesheet URL for a webfont.
698 *
699 * @since 1.1.0
700 *
701 * @param string $url The URL of the remote webfont.
702 * @param string $format The font-format. If you need to support IE, change this to "woff".
703 *
704 * @return string Returns the CSS.
705 */
706 function wptt_get_webfont_url( $url, $format = 'woff2' ) {
707 $font = new WPTT_WebFont_Loader( $url );
708 $font->set_font_format( $format );
709 return $font->get_url();
710 }
711 }
712