PluginProbe
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant / 1.2
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant v1.2
2.0.3 2.0.2 2.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2 1.3 2.0.0
easyfonts / easyfonts.php

easyfonts.php in EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant 1.2, at easyfonts.php

331 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: EasyFonts
4 * Plugin URI: https://easywpstuff.com
5 * Description: Automatically Host existing google fonts locally on your server
6 * Version: 1.2.0
7 * Author: Uzair
8 * Author URI: https://easywpstuff.com
9 * License: GPL2
10 * Text Domain: easyfonts
11 */
12
13 if ( ! defined( 'WPINC' ) ) {
14 die;
15 }
16
17 define( 'EASYFONTS_VERSION', '1.2.0' );
18 define( 'EASYFONTS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
19 define( 'EASYFONTS_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
20 define( 'EASYFONTS_UPLOAD_DIR', wp_upload_dir()['basedir'] . '/easyfonts' );
21 $upload_baseurl = wp_upload_dir()['baseurl'];
22 if ( is_ssl() ) {
23 $upload_baseurl = set_url_scheme( $upload_baseurl, 'https' );
24 }
25 define( 'EASYFONTS_UPLOAD_URL', $upload_baseurl . '/easyfonts' );
26
27 require_once EASYFONTS_PLUGIN_DIR . '/lib/simple_html_dom.php';
28 include_once EASYFONTS_PLUGIN_DIR . '/inc/options.php';
29 include_once EASYFONTS_PLUGIN_DIR . '/inc/notices.php';
30
31 class EasyFonts {
32
33 public function __construct() {
34 add_action( 'plugins_loaded', [ $this, 'load_textdomain' ] );
35 add_action( 'template_redirect', [ $this, 'maybe_start_buffering' ] );
36 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_options_styles' ] );
37 add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), [ $this, 'add_settings_link' ] );
38 register_uninstall_hook( __FILE__, [ __CLASS__, 'uninstall' ] );
39 }
40
41 public function load_textdomain() {
42 load_plugin_textdomain( 'easyfonts', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
43 }
44
45 /**
46 * Get base URL considering SSL.
47 *
48 * @return string
49 */
50 private static function get_base_url() {
51 return is_ssl() ? set_url_scheme( wp_upload_dir()['baseurl'], 'https' ) : wp_upload_dir()['baseurl'];
52 }
53
54 public function maybe_start_buffering() {
55 if ( is_admin() ) {
56 return;
57 }
58
59 if ( $this->should_process() ) {
60 ob_start( [ $this, 'combined_callback' ] );
61 }
62
63 add_filter( 'wordpress_prepare_output', [ $this, 'after_smart_slider' ], 11 );
64 add_filter( 'groovy_menu_final_output', [ $this, 'after_smart_slider' ], 11 );
65 }
66
67 private function should_process() {
68 $options = get_option( 'easyfonts_options', [] );
69 return ! empty( $options['host_link'] ) || ! empty( $options['host_import'] ) || ! empty( $options['process_fontface'] ) || ! empty( $options['remove_hints'] ) || ! empty( $options['remove_scripts'] );
70 }
71
72 public function combined_callback( $buffer ) {
73 $options = get_option( 'easyfonts_options', [] );
74
75 if ( ! empty( $options['host_link'] ) ) {
76 $buffer = $this->process_content_link_tag( $buffer );
77 }
78 if ( ! empty( $options['host_import'] ) ) {
79 $buffer = $this->process_content_import( $buffer );
80 }
81 if ( ! empty( $options['process_fontface'] ) ) {
82 $buffer = $this->download_gstatic_fonts( $buffer );
83 }
84 if ( ! empty( $options['remove_hints'] ) ) {
85 $buffer = $this->remove_resource_hints( $buffer );
86 }
87 if ( ! empty( $options['remove_scripts'] ) ) {
88 $buffer = $this->remove_font_scripts( $buffer );
89 }
90
91 return $buffer;
92 }
93
94 public function after_smart_slider( $buffer ) {
95 $options = get_option( 'easyfonts_options', [] );
96 if ( ! empty( $options['host_link'] ) ) {
97 $buffer = $this->process_content_link_tag( $buffer );
98 }
99 return $buffer;
100 }
101
102 /**
103 * Fetch remote file content.
104 *
105 * @param string $url URL to fetch.
106 * @return string|false Content or false on error.
107 */
108 private function get_remote_file( $url ) {
109 $response = wp_remote_get( $url, [
110 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
111 ] );
112 if ( is_wp_error( $response ) ) {
113 error_log( __( 'EasyFonts remote fetch error: ', 'easyfonts' ) . $response->get_error_message() );
114 return false;
115 }
116 return wp_remote_retrieve_body( $response );
117 }
118
119 /**
120 * Process @font-face in CSS and download fonts.
121 *
122 * @param string $css CSS content.
123 * @return string Processed CSS.
124 */
125 private function process_font_face_declarations( $css ) {
126 if ( preg_match_all( '/@font-face\s*\{([^}]+)\}/', $css, $matches ) ) {
127 foreach ( $matches[1] as $match ) {
128 if ( preg_match_all( '/src\s*:\s*([^;]+);/', $match, $srcs ) ) {
129 foreach ( $srcs[1] as $src ) {
130 if ( preg_match( '/url\(([^)]+)\)/', $src, $url_match ) ) {
131 $font_url = trim( $url_match[1], "'\"" );
132 $font_filename = substr( hash( 'sha256', $font_url ), 0, 10 ) . '.' . pathinfo( $font_url, PATHINFO_EXTENSION );
133 $local_path = EASYFONTS_UPLOAD_DIR . '/' . $font_filename;
134 if ( ! file_exists( $local_path ) ) {
135 $font_data = $this->get_remote_file( $font_url );
136 if ( $font_data !== false ) {
137 file_put_contents( $local_path, $font_data );
138 }
139 }
140 $css = str_replace( $src, "url('" . EASYFONTS_UPLOAD_URL . "/" . $font_filename . "')", $css );
141 }
142 }
143 }
144 }
145 }
146 return $css;
147 }
148
149 private function process_html_with_dom( $content, $callback ) {
150 $html = hgfl_str_get_html( $content, false, true, 'UTF-8', false, PHP_EOL, ' ' );
151 if ( empty( $html ) ) {
152 return $content;
153 }
154 $callback( $html );
155 return $html->save();
156 }
157
158 private function process_content_link_tag( $content ) {
159 return $this->process_html_with_dom( $content, function( $html ) {
160 if ( ! wp_mkdir_p( EASYFONTS_UPLOAD_DIR ) ) {
161 error_log( __( 'EasyFonts could not create directory: ', 'easyfonts' ) . EASYFONTS_UPLOAD_DIR );
162 return;
163 }
164 $apply_bunny = apply_filters( 'easyfonts_bunnyfonts', true );
165 $providers = [ 'fonts.googleapis.com' ];
166 if ( $apply_bunny ) {
167 $providers[] = 'fonts.bunny.net';
168 }
169 foreach ( $html->find( 'link[rel=stylesheet]' ) as $link ) {
170 $href = $link->href;
171 $matched = false;
172 foreach ( $providers as $provider ) {
173 if ( strpos( $href, $provider ) !== false ) {
174 $matched = true;
175 break;
176 }
177 }
178 if ( ! $matched ) {
179 continue;
180 }
181 if ( strpos( $href, '//' ) === 0 ) {
182 $href = 'https:' . $href;
183 }
184 $decoded_url = rawurldecode( htmlspecialchars_decode( $href ) );
185 $filename = substr( hash( 'sha256', $decoded_url ), 0, 10 ) . '.css';
186 $local_path = EASYFONTS_UPLOAD_DIR . '/' . $filename;
187 if ( ! file_exists( $local_path ) ) {
188 $css = $this->get_remote_file( $decoded_url );
189 if ( $css === false ) {
190 continue;
191 }
192 if ( ! preg_match_all( '/@font-face\s*\{([^}]+)\}/', $css, $matches ) ) {
193 continue;
194 }
195 $css = $this->process_font_face_declarations( $css );
196 file_put_contents( $local_path, $css );
197 }
198 $link->href = EASYFONTS_UPLOAD_URL . '/' . $filename;
199 }
200 } );
201 }
202
203 private function process_content_import( $content ) {
204 return $this->process_html_with_dom( $content, function( $html ) {
205 if ( ! wp_mkdir_p( EASYFONTS_UPLOAD_DIR ) ) {
206 error_log( __( 'EasyFonts could not create directory: ', 'easyfonts' ) . EASYFONTS_UPLOAD_DIR );
207 return;
208 }
209 foreach ( $html->find( 'style' ) as $style ) {
210 if ( preg_match_all( '/@import\s+(url\()?\s*([^\)]+)\s*(\))?/', $style->innertext, $matches ) ) {
211 foreach ( $matches[2] as $match ) {
212 if ( strpos( $match, 'fonts.googleapis.com' ) === false ) {
213 continue;
214 }
215 $url = trim( $match, "'\"" );
216 if ( strpos( $url, '//' ) === 0 ) {
217 $url = 'https:' . $url;
218 }
219 $decoded_url = rawurldecode( htmlspecialchars_decode( $url ) );
220 $filename = substr( hash( 'sha256', $decoded_url ), 0, 10 ) . '.css';
221 $local_path = EASYFONTS_UPLOAD_DIR . '/' . $filename;
222 if ( ! file_exists( $local_path ) ) {
223 $css = $this->get_remote_file( $decoded_url );
224 if ( $css === false ) {
225 continue;
226 }
227 $css = $this->process_font_face_declarations( $css );
228 file_put_contents( $local_path, $css );
229 }
230 $style->innertext = str_replace( $match, EASYFONTS_UPLOAD_URL . '/' . $filename, $style->innertext );
231 }
232 }
233 }
234 } );
235 }
236
237 private function remove_resource_hints( $content ) {
238 return $this->process_html_with_dom( $content, function( $html ) {
239 $links = $html->find( 'link[rel=preload], link[rel=preconnect], link[rel=dns-prefetch]' );
240 foreach ( $links as $link ) {
241 if ( preg_match( '/(https:\/\/|\/\/)(fonts\.googleapis\.com|fonts\.gstatic\.com)/', $link->href ) ) {
242 $link->outertext = '';
243 }
244 }
245 $styles = $html->find( 'style' );
246 foreach ( $styles as $style ) {
247 if ( strpos( $style->innertext, 'fonts.googleapis.com' ) !== false || strpos( $style->innertext, 'fonts.gstatic.com' ) !== false ) {
248 $style->innertext = preg_replace( '#/\*(?:[^*]*(?:\*(?!/))*)*\*/#', '', $style->innertext );
249 }
250 }
251 } );
252 }
253
254 private function remove_font_scripts( $content ) {
255 return $this->process_html_with_dom( $content, function( $html ) {
256 $scripts = $html->find( 'script' );
257 foreach ( $scripts as $script ) {
258 if ( strpos( $script->innertext, 'WebFontConfig' ) !== false || strpos( $script->innertext, 'webfont.js' ) !== false ) {
259 $script->outertext = '';
260 }
261 }
262 } );
263 }
264
265 private function download_gstatic_fonts( $content ) {
266 if ( ! wp_mkdir_p( EASYFONTS_UPLOAD_DIR ) ) {
267 error_log( __( 'EasyFonts could not create directory: ', 'easyfonts' ) . EASYFONTS_UPLOAD_DIR );
268 return $content;
269 }
270 $content = preg_replace( '/(url\s?\(["\']?)\/\/(fonts\.gstatic\.com[^"\']+)/', '$1https://$2', $content );
271 $html = hgfl_str_get_html( $content, false, true, 'UTF-8', false, PHP_EOL, ' ' );
272 if ( empty( $html ) ) {
273 return $content;
274 }
275 foreach ( $html->find( 'style' ) as $style ) {
276 if ( preg_match_all( '/url\(([^)]+)\)/', $style->innertext, $matches ) ) {
277 foreach ( $matches[1] as $match ) {
278 $url = trim( $match, "'\"" );
279 if ( strpos( $url, '//' ) === 0 ) {
280 $url = 'https:' . $url;
281 }
282 if ( strpos( $url, 'fonts.gstatic.com' ) === false ) {
283 continue;
284 }
285 $path_parts = pathinfo( $url );
286 $extension = $path_parts['extension'] ?? '';
287 if ( empty( $extension ) ) {
288 $extension = strpos( $url, '/l/font' ) !== false ? 'svg' : 'woff2';
289 }
290 $filename = substr( hash( 'sha256', $url ), 0, 10 ) . '.' . $extension;
291 $local_path = EASYFONTS_UPLOAD_DIR . '/' . $filename;
292 if ( ! file_exists( $local_path ) ) {
293 $font_data = $this->get_remote_file( $url );
294 if ( $font_data !== false ) {
295 file_put_contents( $local_path, $font_data );
296 }
297 }
298 $content = str_replace( $url, EASYFONTS_UPLOAD_URL . '/' . $filename, $content );
299 }
300 }
301 }
302 return $content;
303 }
304
305 public function enqueue_options_styles() {
306 if ( 'settings_page_easyfonts' === get_current_screen()->id ) {
307 wp_enqueue_style( 'easyfonts-options-styles', EASYFONTS_PLUGIN_URL . 'assets/style.css', [], EASYFONTS_VERSION );
308 }
309 }
310
311 public function add_settings_link( $links ) {
312 $settings_link = '<a href="options-general.php?page=easyfonts">' . __( 'Settings', 'easyfonts' ) . '</a>';
313 array_unshift( $links, $settings_link );
314 return $links;
315 }
316
317 public static function uninstall() {
318 delete_option( 'easyfonts_options' );
319 $dir = EASYFONTS_UPLOAD_DIR;
320 if ( is_dir( $dir ) ) {
321 $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST );
322 foreach ( $files as $fileinfo ) {
323 $todo = ( $fileinfo->isDir() ? 'rmdir' : 'unlink' );
324 $todo( $fileinfo->getRealPath() );
325 }
326 rmdir( $dir );
327 }
328 }
329 }
330
331 new EasyFonts();