Make WordPress Themes

source: travel-booking-agency/0.3/wptt-webfont-loader.php

Last change on this file was 201402, checked in by themedropbox, 2 years ago

New version of Travel Booking Agency - 0.2.2

File size: 16.9 KB
Line 
1<?php
2/**
3 * Download webfonts locally.
4 *
5 * @package wptt/font-loader
6 * @license https://opensource.org/licenses/MIT
7 */
8
9if ( ! class_exists( 'travel_booking_agency_WPTT_WebFont_Loader' ) ) {
10        /**
11         * Download webfonts locally.
12         */
13        class travel_booking_agency_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                                        // Check if the file already exists.
313                                        if ( file_exists( $font_path ) ) {
314
315                                                // Skip if already cached.
316                                                if ( isset( $stored[ $url ] ) ) {
317                                                        continue;
318                                                }
319
320                                                // Add file to the cache and change the $changed var to indicate we need to update the option.
321                                                $stored[ $url ] = $font_path;
322                                                $change         = true;
323
324                                                // Since the file exists we don't need to proceed with downloading it.
325                                                continue;
326                                        }
327
328                                        /**
329                                         * If we got this far, we need to download the file.
330                                         */
331
332                                        // require file.php if the download_url function doesn't exist.
333                                        if ( ! function_exists( 'download_url' ) ) {
334                                                require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' );
335                                        }
336
337                                        // Download file to temporary location.
338                                        $tmp_path = download_url( $url );
339
340                                        // Make sure there were no errors.
341                                        if ( is_wp_error( $tmp_path ) ) {
342                                                continue;
343                                        }
344
345                                        // Move temp file to final destination.
346                                        $success = $this->get_filesystem()->move( $tmp_path, $font_path, true );
347                                        if ( $success ) {
348                                                $stored[ $url ] = $font_path;
349                                                $change         = true;
350                                        }
351                                }
352                        }
353
354                        // If there were changes, update the option.
355                        if ( $change ) {
356
357                                // Cleanup the option and then save it.
358                                foreach ( $stored as $url => $path ) {
359                                        if ( ! file_exists( $path ) ) {
360                                                unset( $stored[ $url ] );
361                                        }
362                                }
363                                update_site_option( 'downloaded_font_files', $stored );
364                        }
365
366                        return $stored;
367                }
368
369                /**
370                 * Get font files from the CSS.
371                 *
372                 * @access public
373                 * @since 1.0.0
374                 * @return array Returns an array of font-families and the font-files used.
375                 */
376                public function get_remote_files_from_css() {
377
378                        $font_faces = explode( '@font-face', $this->remote_styles );
379
380                        $result = array();
381
382                        // Loop all our font-face declarations.
383                        foreach ( $font_faces as $font_face ) {
384
385                                // Make sure we only process styles inside this declaration.
386                                $style = explode( '}', $font_face )[0];
387
388                                // Sanity check.
389                                if ( false === strpos( $style, 'font-family' ) ) {
390                                        continue;
391                                }
392
393                                // Get an array of our font-families.
394                                preg_match_all( '/font-family.*?\;/', $style, $matched_font_families );
395
396                                // Get an array of our font-files.
397                                preg_match_all( '/url\(.*?\)/i', $style, $matched_font_files );
398
399                                // Get the font-family name.
400                                $font_family = 'unknown';
401                                if ( isset( $matched_font_families[0] ) && isset( $matched_font_families[0][0] ) ) {
402                                        $font_family = rtrim( ltrim( $matched_font_families[0][0], 'font-family:' ), ';' );
403                                        $font_family = trim( str_replace( array( "'", ';' ), '', $font_family ) );
404                                        $font_family = sanitize_key( strtolower( str_replace( ' ', '-', $font_family ) ) );
405                                }
406
407                                // Make sure the font-family is set in our array.
408                                if ( ! isset( $result[ $font_family ] ) ) {
409                                        $result[ $font_family ] = array();
410                                }
411
412                                // Get files for this font-family and add them to the array.
413                                foreach ( $matched_font_files as $match ) {
414
415                                        // Sanity check.
416                                        if ( ! isset( $match[0] ) ) {
417                                                continue;
418                                        }
419
420                                        // Add the file URL.
421                                        $font_family_url = rtrim( ltrim( $match[0], 'url(' ), ')' );
422
423                                        // Make sure to convert relative URLs to absolute.
424                                        $font_family_url = $this->get_absolute_path( $font_family_url );
425
426                                        $result[ $font_family ][] = $font_family_url;
427                                }
428
429                                // Make sure we have unique items.
430                                // We're using array_flip here instead of array_unique for improved performance.
431                                $result[ $font_family ] = array_flip( array_flip( $result[ $font_family ] ) );
432                        }
433                        return $result;
434                }
435
436                /**
437                 * Write the CSS to the filesystem.
438                 *
439                 * @access protected
440                 * @since 1.1.0
441                 * @return string|false Returns the absolute path of the file on success, or false on fail.
442                 */
443                protected function write_stylesheet() {
444                        $file_path  = $this->get_local_stylesheet_path();
445                        $filesystem = $this->get_filesystem();
446
447                        if ( ! defined( 'FS_CHMOD_DIR' ) ) {
448                                define( 'FS_CHMOD_DIR', ( 0755 & ~ umask() ) );
449                        }
450
451                        // If the folder doesn't exist, create it.
452                        if ( ! file_exists( $this->get_fonts_folder() ) ) {
453                                $this->get_filesystem()->mkdir( $this->get_fonts_folder(), FS_CHMOD_DIR );
454                        }
455
456                        // If the file doesn't exist, create it. Return false if it can not be created.
457                        if ( ! $filesystem->exists( $file_path ) && ! $filesystem->touch( $file_path ) ) {
458                                return false;
459                        }
460
461                        // If we got this far, we need to write the file.
462                        // Get the CSS.
463                        if ( ! $this->css ) {
464                                $this->get_styles();
465                        }
466
467                        // Put the contents in the file. Return false if that fails.
468                        if ( ! $filesystem->put_contents( $file_path, $this->css ) ) {
469                                return false;
470                        }
471
472                        return $file_path;
473                }
474
475                /**
476                 * Get the stylesheet path.
477                 *
478                 * @access public
479                 * @since 1.1.0
480                 * @return string
481                 */
482                public function get_local_stylesheet_path() {
483                        if ( ! $this->local_stylesheet_path ) {
484                                $this->local_stylesheet_path = $this->get_fonts_folder() . '/' . $this->get_local_stylesheet_filename() . '.css';
485                        }
486                        return $this->local_stylesheet_path;
487                }
488
489                /**
490                 * Get the local stylesheet filename.
491                 *
492                 * This is a hash, generated from the site-URL, the wp-content path and the URL.
493                 * This way we can avoid issues with sites changing their URL, or the wp-content path etc.
494                 *
495                 * @access public
496                 * @since 1.1.0
497                 * @return string
498                 */
499                public function get_local_stylesheet_filename() {
500                        return md5( $this->get_base_url() . $this->get_base_path() . $this->remote_url . $this->font_format );
501                }
502
503                /**
504                 * Set the font-format to be used.
505                 *
506                 * @access public
507                 * @since 1.0.0
508                 * @param string $format The format to be used. Use "woff" or "woff2".
509                 * @return void
510                 */
511                public function set_font_format( $format = 'woff2' ) {
512                        $this->font_format = $format;
513                }
514
515                /**
516                 * Check if the local stylesheet exists.
517                 *
518                 * @access public
519                 * @since 1.1.0
520                 * @return bool
521                 */
522                public function local_file_exists() {
523                        return ( ! file_exists( $this->get_local_stylesheet_path() ) );
524                }
525
526                /**
527                 * Get the base path.
528                 *
529                 * @access public
530                 * @since 1.1.0
531                 * @return string
532                 */
533                public function get_base_path() {
534                        if ( ! $this->base_path ) {
535                                $this->base_path = apply_filters( 'wptt_get_local_fonts_base_path', $this->get_filesystem()->wp_content_dir() );
536                        }
537                        return $this->base_path;
538                }
539
540                /**
541                 * Get the base URL.
542                 *
543                 * @access public
544                 * @since 1.1.0
545                 * @return string
546                 */
547                public function get_base_url() {
548                        if ( ! $this->base_url ) {
549                                $this->base_url = apply_filters( 'wptt_get_local_fonts_base_url', content_url() );
550                        }
551                        return $this->base_url;
552                }
553
554                /**
555                 * Get the subfolder name.
556                 *
557                 * @access public
558                 * @since 1.1.0
559                 * @return string
560                 */
561                public function get_subfolder_name() {
562                        if ( ! $this->subfolder_name ) {
563                                $this->subfolder_name = apply_filters( 'wptt_get_local_fonts_subfolder_name', 'fonts' );
564                        }
565                        return $this->subfolder_name;
566                }
567
568                /**
569                 * Get the folder for fonts.
570                 *
571                 * @access public
572                 * @return string
573                 */
574                public function get_fonts_folder() {
575                        if ( ! $this->fonts_folder ) {
576                                $this->fonts_folder = $this->get_base_path();
577                                if ( $this->get_subfolder_name() ) {
578                                        $this->fonts_folder .= '/' . $this->get_subfolder_name();
579                                }
580                        }
581                        return $this->fonts_folder;
582                }
583
584                /**
585                 * Schedule a cleanup.
586                 *
587                 * Deletes the fonts files on a regular basis.
588                 * This way font files will get updated regularly,
589                 * and we avoid edge cases where unused files remain in the server.
590                 *
591                 * @access public
592                 * @since 1.1.0
593                 * @return void
594                 */
595                public function schedule_cleanup() {
596                        if ( ! is_multisite() || ( is_multisite() && is_main_site() ) ) {
597                                if ( ! wp_next_scheduled( 'delete_fonts_folder' ) && ! wp_installing() ) {
598                                        wp_schedule_event( time(), self::CLEANUP_FREQUENCY, 'delete_fonts_folder' );
599                                }
600                        }
601                }
602
603                /**
604                 * Delete the fonts folder.
605                 *
606                 * This runs as part of a cleanup routine.
607                 *
608                 * @access public
609                 * @since 1.1.0
610                 * @return bool
611                 */
612                public function delete_fonts_folder() {
613                        return $this->get_filesystem()->delete( $this->get_fonts_folder(), true );
614                }
615
616                /**
617                 * Get the filesystem.
618                 *
619                 * @access protected
620                 * @since 1.0.0
621                 * @return \WP_Filesystem_Base
622                 */
623                protected function get_filesystem() {
624                        global $wp_filesystem;
625
626                        // If the filesystem has not been instantiated yet, do it here.
627                        if ( ! $wp_filesystem ) {
628                                if ( ! function_exists( 'WP_Filesystem' ) ) {
629                                        require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' );
630                                }
631                                WP_Filesystem();
632                        }
633                        return $wp_filesystem;
634                }
635
636                /**
637                 * Get an absolute URL from a relative URL.
638                 *
639                 * @access protected
640                 *
641                 * @param string $url The URL.
642                 *
643                 * @return string
644                 */
645                protected function get_absolute_path( $url ) {
646
647                        // If dealing with a root-relative URL.
648                        if ( 0 === stripos( $url, '/' ) ) {
649                                $parsed_url = parse_url( $this->remote_url );
650                                return $parsed_url['scheme'] . '://' . $parsed_url['hostname'] . $url;
651                        }
652
653                        return $url;
654                }
655        }
656}
657
658if ( ! function_exists( 'wptt_get_webfont_styles' ) ) {
659        /**
660         * Get styles for a webfont.
661         *
662         * This will get the CSS from the remote API,
663         * download any fonts it contains,
664         * replace references to remote URLs with locally-downloaded assets,
665         * and finally return the resulting CSS.
666         *
667         * @since 1.0.0
668         *
669         * @param string $url    The URL of the remote webfont.
670         * @param string $format The font-format. If you need to support IE, change this to "woff".
671         *
672         * @return string Returns the CSS.
673         */
674        function wptt_get_webfont_styles( $url, $format = 'woff2' ) {
675                $font = new travel_booking_agency_WPTT_WebFont_Loader( $url );
676                $font->set_font_format( $format );
677                return $font->get_styles();
678        }
679}
680
681if ( ! function_exists( 'wptt_get_webfont_url' ) ) {
682        /**
683         * Get a stylesheet URL for a webfont.
684         *
685         * @since 1.1.0
686         *
687         * @param string $url    The URL of the remote webfont.
688         * @param string $format The font-format. If you need to support IE, change this to "woff".
689         *
690         * @return string Returns the CSS.
691         */
692        function wptt_get_webfont_url( $url, $format = 'woff2' ) {
693                $font = new travel_booking_agency_WPTT_WebFont_Loader( $url );
694                $font->set_font_format( $format );
695                return $font->get_url();
696        }
697}
Note: See TracBrowser for help on using the repository browser.