PluginProbe
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant / 2.0.4
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant v2.0.4
2.0.4 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
← All changes | easyfonts.php +173 -311 1.1.1 → 2.0.4 View file →
@@ -1,342 +1,204 @@
1 1 <?php
2 -/*
3 -Plugin Name: EasyFonts
4 -Plugin URI:
5 -Description: Automatically Host existing google fonts locally on your server
6 -Version: 1.1.1
7 -Author: Uzair
8 -Author URI: https://easywpstuff.com
9 -License: GPL2
10 -*/
2 +/**
3 + * Plugin Name: Easy Fonts
4 + * Plugin URI: https://fluxpress.io
5 + * Description: Detect, self-host, preload, and trim Google Fonts automatically — reliably, with zero-CLS metric-matched fallbacks. Built on WordPress's native HTML API.
6 + * Version: 2.0.4
7 + * Author: Uzair
8 + * Author URI: https://fluxpress.io
9 + * License: GPL-2.0-or-later
10 + * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11 + * Text Domain: easyfonts
12 + * Domain Path: /languages
13 + * Requires at least: 6.4
14 + * Requires PHP: 7.4
15 + *
16 + * @package EasyFonts
17 + */
11 18
12 -// If this file is called directly, abort.
13 -if (!defined("WPINC")){
14 - die;
19 +defined( 'ABSPATH' ) || exit;
20 +
21 +define( 'EASYFONTS_VERSION', '2.0.4' );
22 +define( 'EASYFONTS_FILE', __FILE__ );
23 +define( 'EASYFONTS_DIR', plugin_dir_path( __FILE__ ) );
24 +define( 'EASYFONTS_URL', plugin_dir_url( __FILE__ ) );
25 +define( 'EASYFONTS_BASENAME', plugin_basename( __FILE__ ) );
26 +
27 +/**
28 + * PHP 7.4 polyfills. The plugin advertises "Requires PHP: 7.4", but parts of the
29 + * codebase use string helpers added in PHP 8.0. These guarded shims keep it
30 + * genuinely 7.4-compatible without changing any call sites.
31 + */
32 +if ( ! function_exists( 'str_starts_with' ) ) {
33 + function str_starts_with( $haystack, $needle ): bool {
34 + return '' === $needle || 0 === strncmp( (string) $haystack, (string) $needle, strlen( (string) $needle ) );
35 + }
15 36 }
37 +if ( ! function_exists( 'str_ends_with' ) ) {
38 + function str_ends_with( $haystack, $needle ): bool {
39 + if ( '' === $needle ) {
40 + return true;
41 + }
42 + $len = strlen( (string) $needle );
43 + return $len <= strlen( (string) $haystack ) && 0 === substr_compare( (string) $haystack, (string) $needle, -$len );
44 + }
45 +}
46 +if ( ! function_exists( 'str_contains' ) ) {
47 + function str_contains( $haystack, $needle ): bool {
48 + return '' === $needle || false !== strpos( (string) $haystack, (string) $needle );
49 + }
50 +}
16 51
17 -require_once dirname(__FILE__) . '/lib/simple_html_dom.php';
52 +/**
53 + * PSR-4 autoloader for the EasyFonts\ namespace.
54 + */
55 +spl_autoload_register(
56 + static function ( $class ) {
57 + $prefix = 'EasyFonts\\';
58 + $len = strlen( $prefix );
18 59
19 -include_once dirname(__FILE__) . '/inc/options.php';
60 + if ( 0 !== strncmp( $prefix, $class, $len ) ) {
61 + return;
62 + }
20 63
21 -include_once dirname(__FILE__) . '/inc/notices.php';
64 + $relative = substr( $class, $len );
65 + $path = EASYFONTS_DIR . 'src/' . str_replace( '\\', '/', $relative ) . '.php';
22 66
23 -function get_base_url() {
24 - if (is_ssl()) {
25 - $base_url = set_url_scheme(wp_upload_dir()['baseurl'], 'https');
26 - } else {
27 - $base_url = wp_upload_dir()['baseurl'];
28 - }
29 - return $base_url;
30 -}
67 + if ( is_readable( $path ) ) {
68 + require_once $path;
69 + }
70 + }
71 +);
31 72
32 -function get_basecss_url() {
33 - if (is_ssl()) {
34 - $basecss_url = set_url_scheme(wp_upload_dir()['baseurl'], 'https');
35 - } else {
36 - $basecss_url = wp_upload_dir()['baseurl'];
37 - }
38 - return $basecss_url;
39 -}
73 +/**
74 + * Resolve the upload directory + URL once. Scheme-correct, multisite-safe.
75 + *
76 + * @return array{path:string,url:string}
77 + */
78 +function easyfonts_paths(): array {
79 + static $paths = null;
40 80
41 -function wp_get_remote_css($decoded_url) {
42 - $backtrace = debug_backtrace();
43 - $function_name = isset($backtrace[1]['function']) ? $backtrace[1]['function'] : '';
44 -
45 - $response = wp_remote_get($decoded_url, array('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',));
46 -
47 - if (is_wp_error($response)) {
48 - add_settings_error($function_name, 'http_error', $response->get_error_message(), 'error');
49 - return false;
50 - }
51 -
52 - $getcss = wp_remote_retrieve_body($response);
53 - return $getcss;
54 -}
81 + if ( null === $paths ) {
82 + $upload = wp_upload_dir();
83 + $paths = array(
84 + 'path' => trailingslashit( $upload['basedir'] ) . 'easyfonts',
85 + 'url' => set_url_scheme( trailingslashit( $upload['baseurl'] ) . 'easyfonts' ),
86 + );
87 + }
55 88
56 -function wp_get_remote_font($font_url) {
57 - $backtrace = debug_backtrace();
58 - $function_name = isset($backtrace[1]['function']) ? $backtrace[1]['function'] : '';
59 - $font_response = wp_remote_get($font_url, array('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',));
60 - if (is_wp_error($font_response)) {
61 - add_settings_error($function_name, 'http_error', $font_response->get_error_message(), 'error');
62 - return false;
63 - }
64 - $getfont_data = wp_remote_retrieve_body($font_response);
65 - return $getfont_data;
89 + return $paths;
66 90 }
67 91
68 -function easyfont_process_html_with_dom($content, $callback) {
69 - // using simple html dom
70 - $html = hgfl_str_get_html($content, false, true, 'UTF-8', false, PHP_EOL, ' ');
71 -
72 - if (empty($html)) {
73 - return $content;
74 - }
75 -
76 - $callback($html);
92 +/**
93 + * Boot the plugin on plugins_loaded.
94 + */
95 +add_action(
96 + 'plugins_loaded',
97 + static function () {
98 + $paths = easyfonts_paths();
77 99
78 - $content = $html->save();
79 - return $content;
80 -}
100 + if ( ! defined( 'EASYFONTS_CACHE_DIR' ) ) {
101 + define( 'EASYFONTS_CACHE_DIR', $paths['path'] );
102 + }
103 + if ( ! defined( 'EASYFONTS_CACHE_URL' ) ) {
104 + define( 'EASYFONTS_CACHE_URL', $paths['url'] );
105 + }
81 106
82 -function easyfont_process_content_link_tag($content) {
83 - return easyfont_process_html_with_dom($content, function($html) {
84 - // Check if the 'easyfonts' directory exists and is writable
85 - $easyfonts_dir = wp_upload_dir() ['basedir'] . '/easyfonts';
86 - if (!wp_mkdir_p($easyfonts_dir)) {
87 - return $content;
88 - }
89 - // Find all <link> tags with a rel attribute of 'stylesheet' and a href attribute that points to a Google Fonts stylesheet
90 - foreach ($html->find('link[rel=stylesheet][href*=fonts.googleapis.com]') as $link) {
91 - $url = $link->href;
92 - if(strpos($url,'//') === 0){
93 - $url = 'https:'.$url;
94 - }
95 - $decoded_url = rawurldecode(htmlspecialchars_decode($url));
96 - // Generate a local filename for the stylesheet
97 - $filename = hash('sha256', $decoded_url, false);
98 - $filename = substr($filename, 0, 10) . '.css';
99 - if (!file_exists($easyfonts_dir . '/' . $filename)) {
100 - $css = wp_get_remote_css($decoded_url);
101 - if (!preg_match_all('/@font-face\s*\{([^}]+)\}/', $css, $matches)) continue;
107 + \EasyFonts\Plugin::instance();
108 + }
109 +);
102 110
103 - if (preg_match_all('/@font-face\s*\{([^}]+)\}/', $css, $matches)) {
104 - foreach ($matches[1] as $match) {
105 - if (preg_match_all('/src\s*:\s*([^;]+);/', $match, $srcs)) {
106 - foreach ($srcs[1] as $src) {
107 - if (preg_match('/url\(([^)]+)\)/', $src, $url_match)) {
108 - $font_url = trim($url_match[1], "'\"");
109 - $font_filename = hash('sha256', $font_url, false);
110 - $font_filename = substr($font_filename, 0, 10) . '.' . pathinfo($font_url, PATHINFO_EXTENSION);
111 - if (!file_exists($easyfonts_dir . '/' . $font_filename)) {
112 - $font_data = wp_get_remote_font($font_url);
113 - // Save the font file to the 'wp-content/uploads/easyfonts' directory
114 - file_put_contents($easyfonts_dir . '/' . $font_filename, $font_data);
115 - }
116 - // Replace the src property with a local reference to the downloaded font file
117 - $finalbase = get_base_url();
118 -$css = str_replace($src, "url('" . $finalbase . "/easyfonts/" . $font_filename . "')", $css);
119 - }
120 - }
121 - }
122 - }
123 - }
124 - // Save the modified stylesheet to the 'wp-content/uploads/easyfonts' directory
125 - file_put_contents($easyfonts_dir . '/' . $filename, $css);
126 - }
127 - $finalbasecss = get_basecss_url();
128 - // Replace the href attribute with a local reference to the downloaded stylesheet
129 - $link->href = $finalbasecss . '/easyfonts/' . $filename;
130 - }
131 - // Return the modified content
132 - });
111 +/**
112 + * WP-CLI registration (loaded outside the normal request lifecycle).
113 + */
114 +if ( defined( 'WP_CLI' ) && WP_CLI ) {
115 + add_action(
116 + 'cli_init',
117 + static function () {
118 + \WP_CLI::add_command( 'easyfonts', \EasyFonts\Cli\Command::class );
119 + }
120 + );
133 121 }
134 122
135 -function easyfont_process_content_import($content) {
136 - // using simple html dom
137 - return easyfont_process_html_with_dom($content, function($html) {
138 - // Check if the 'easyfonts' directory exists and is writable
139 - $easyfonts_dir = wp_upload_dir() ['basedir'] . '/easyfonts';
140 - if (!wp_mkdir_p($easyfonts_dir)) {
141 - return $content;
142 - }
143 - // Find all <style> tags with @import statements that import a Google Fonts stylesheet
144 - foreach ($html->find('style') as $style) {
145 - if (preg_match_all('/@import\s+(url\()?\s*([^\)]+)\s*(\))?/', $style->innertext, $matches)) {
146 - foreach ($matches[2] as $match) {
147 - if (strpos($match, 'fonts.googleapis.com') !== false) {
148 - $url = trim($match, "'\"");
149 - if (strpos($url, '//') === 0) {
150 - $url = 'https:' . $url;
151 - }
152 - $decoded_url = rawurldecode(htmlspecialchars_decode($url));
153 - $filename = hash('sha256', $decoded_url, false);
154 - $filename = substr($filename, 0, 10) . '.css';
155 - // Check if the stylesheet file already exists
156 - if (!file_exists($easyfonts_dir . '/' . $filename)) {
157 - $css = wp_get_remote_css($decoded_url);
158 - if (preg_match_all('/@font-face\s*\{([^}]+)\}/', $css, $matches)) {
159 - foreach ($matches[1] as $match) {
160 - if (preg_match_all('/src\s*:\s*([^;]+);/', $match, $srcs)) {
161 - foreach ($srcs[1] as $src) {
162 - if (preg_match('/url\(([^)]+)\)/', $src, $url_match)) {
163 - $font_url = trim($url_match[1], "'\"");
164 - $font_filename = hash('sha256', $font_url, false);
165 - $font_filename = substr($font_filename, 0, 10) . '.' . pathinfo($font_url, PATHINFO_EXTENSION);
166 - // Check if the font file already exists
167 - if (!file_exists($easyfonts_dir . '/' . $font_filename)) {
168 -
169 - $font_data = wp_get_remote_font($font_url);
170 - $file = fopen($easyfonts_dir . '/' . $font_filename, 'w');
171 - fwrite($file, $font_data);
172 - fclose($file);
173 - }
174 - $finalbase = get_base_url();
175 - $css = str_replace($src, "url('" . $finalbase . "/easyfonts/" . $font_filename . "')", $css);
176 - }
177 - }
178 - }
179 - }
180 - }
181 - // Save the modified stylesheet to the 'wp-content/uploads/easyfonts' directory
182 - file_put_contents($easyfonts_dir . '/' . $filename, $css);
183 - }
184 - $finalbasecss = get_basecss_url();
185 - // Replace the @import statement with a local reference to the downloaded stylesheet
186 - $style->innertext = str_replace($match, "" . $finalbasecss . '/easyfonts/' . $filename . "", $style->innertext);
187 - }
188 - }
189 - }
190 - }
191 - // Return the modified content
192 - });
193 -}
194 -function easyfonts_remove_resource_hints($content) {
195 - // Load the HTML into the Simple HTML DOM library
196 - return easyfont_process_html_with_dom($content, function($html) {
197 -
198 - // Find all <link> elements with the preload, preconnect, or prefetch attributes
199 - $links = $html->find('link[rel=preload],link[rel=preconnect],link[rel=dns-prefetch]');
200 -
201 - // Loop through the <link> elements
202 - foreach($links as $link) {
203 - // Check if the <link> element has a href attribute with the fonts.googleapis.com or fonts.gstatic.com domain
204 - if (preg_match('/(https:\/\/|\/\/)(fonts\.googleapis\.com|fonts\.gstatic\.com)/', $link->href)) {
205 - // Remove the <link> element
206 - $link->outertext = '';
207 - }
208 - }
209 -
210 - // Find all <style> elements
211 - $styles = $html->find('style');
212 -
213 - // Loop through the <style> elements
214 - foreach($styles as $style) {
215 - // Remove comments that contain the fonts.googleapis.com or fonts.gstatic.com domain
216 - if (strpos($style->innertext, 'fonts.googleapis.com') !== false || strpos($style->innertext, 'fonts.gstatic.com') !== false) {
217 - $style->innertext = preg_replace('#/\*(?:[^*]*(?:\*(?!/))*)*\*/#','',$style->innertext);
218 - }
219 - }
220 - // Return the modified HTML
221 - });
222 -}
123 +/**
124 + * Activation — create tables, seed defaults, mark for rewrite.
125 + *
126 + * On multisite the activation hook fires once even for a network-wide enable,
127 + * so when network-activated we install on every existing site. New sites
128 + * created later are handled by the wp_initialize_site hook below, and any site
129 + * that somehow slips through self-heals on its first load (Plugin::maybe_migrate).
130 + *
131 + * @param bool $network_wide Whether the plugin is being network-activated.
132 + */
133 +register_activation_hook(
134 + __FILE__,
135 + static function ( $network_wide = false ) {
136 + require_once EASYFONTS_DIR . 'src/Database/Migrator.php';
223 137
138 + $install = static function () {
139 + ( new \EasyFonts\Database\Migrator() )->install();
140 + add_option( 'easyfonts_activated_at', time() );
141 + };
224 142
225 -function easyfont_remove_font_scripts($content) {
226 - // Load the HTML string into a Simple HTML DOM object
227 - return easyfont_process_html_with_dom($content, function($html) {
228 -
229 - // Find all `script` elements
230 - $scripts = $html->find('script');
231 - foreach($scripts as $script) {
232 - // Check if the `script` element contains the `WebFontConfig` or `webfont.js` strings
233 - if(strpos($script->innertext, 'WebFontConfig') !== false || strpos($script->innertext, 'webfont.js') !== false) {
234 - // Remove the `script` element
235 - $script->outertext = '';
236 - }
237 - }
238 -
239 - // Return the modified HTML as a string
240 - });
241 -}
143 + if ( is_multisite() && $network_wide ) {
144 + $site_ids = get_sites( array( 'fields' => 'ids', 'number' => 0 ) );
242 145
243 -function easyfont_download_gstatic_fonts($content) {
244 - // Create the "easyfonts" directory if it doesn't exist
245 - $easyfonts_dir = wp_upload_dir()['basedir'] . '/easyfonts';
246 - if (!wp_mkdir_p($easyfonts_dir)) {
247 - return $content;
248 - }
249 - $content = preg_replace('/(url\s?\(["\']?)\/\/(fonts\.gstatic\.com[^"\']+)/', '$1https://$2', $content);
250 - $html = hgfl_str_get_html($content, false, true, 'UTF-8', false, PHP_EOL, ' ');
251 - foreach ($html->find('style') as $style) {
252 - if (preg_match_all('/url\(([^)]+)\)/', $style->innertext, $matches)) {
253 - foreach ($matches[1] as $match) {
254 - $url = trim($match, "'\"");
255 - if(strpos($url,'//') === 0){
256 - $url = 'https:'.$url;
257 - }
258 - if (strpos($url, 'fonts.gstatic.com') !== false) {
259 - $path_parts = pathinfo($url);
260 - $extension = isset($path_parts['extension'])?$path_parts['extension']:'';
261 - if (empty($extension)) {
262 - if(strpos($url,'/l/font') !== false){
263 - $extension = 'svg';
264 - }else{
265 - $extension = 'woff2';
266 - }
267 - }
268 - $filename = hash('sha256', $url, false);
269 - $filename = substr($filename, 0, 10) . '.' . $extension;
270 - // Check if the font file already exists
271 - if (!file_exists($easyfonts_dir . '/' . $filename)) {
272 - $font_data = wp_get_remote_font($url);
273 - $file = fopen($easyfonts_dir . '/' . $filename, 'w');
274 - fwrite($file, $font_data);
275 - fclose($file);
276 - }
277 - $finalbase = get_base_url();
278 - $local_url = $finalbase . '/easyfonts/' . $filename;
279 - $content = str_replace($url, $local_url, $content);
280 - }
281 - }
282 - }
283 - }
284 - return $content;
285 -}
146 + foreach ( $site_ids as $site_id ) {
147 + switch_to_blog( (int) $site_id );
148 + $install();
149 + restore_current_blog();
150 + }
286 151
152 + return;
153 + }
287 154
288 -function easy_fonts_combined_callback($buffer) {
289 - if (get_option('easyfonts_host_google_fonts_locally_link', false)) {
290 - $buffer = easyfont_process_content_link_tag($buffer);
291 - }
155 + $install();
156 + }
157 +);
292 158
293 - if (get_option('easyfonts_host_google_fonts_locally_import', false)) {
294 - $buffer = easyfont_process_content_import($buffer);
295 - }
159 +/**
160 + * Multisite: install tables when a brand-new subsite is created, if the plugin
161 + * is network-active. (WP 5.1+ passes a WP_Site; older signatures are tolerated.)
162 + *
163 + * @param mixed $site New site (WP_Site) or blog id.
164 + */
165 +add_action(
166 + 'wp_initialize_site',
167 + static function ( $site ) {
168 + if ( ! is_multisite() ) {
169 + return;
170 + }
296 171
297 - if (get_option('easyfonts_remove_inline_css_fontface', false)) {
298 - $buffer = easyfont_download_gstatic_fonts($buffer);
299 - }
172 + if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
173 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
174 + }
300 175
301 - if (get_option('easyfonts_remove_resource_hints', false)) {
302 - $buffer = easyfonts_remove_resource_hints($buffer);
303 - }
176 + if ( ! is_plugin_active_for_network( EASYFONTS_BASENAME ) ) {
177 + return;
178 + }
304 179
305 - if (get_option('easyfonts_remove_inline_script_font', false)) {
306 - $buffer = easyfont_remove_font_scripts($buffer);
307 - }
180 + $blog_id = is_object( $site ) && isset( $site->blog_id ) ? (int) $site->blog_id : (int) $site;
308 181
309 - return $buffer;
310 -}
182 + if ( $blog_id <= 0 ) {
183 + return;
184 + }
311 185
312 -function easy_fonts_run_template_redirect() {
313 -
314 - ob_start('easy_fonts_combined_callback', 0, PHP_OUTPUT_HANDLER_REMOVABLE);
315 - add_filter( 'wordpress_prepare_output', 'easyfont_run_after_smart_slider', 11 );
316 - add_filter('groovy_menu_final_output', 'easyfont_run_after_smart_slider', 11);
186 + require_once EASYFONTS_DIR . 'src/Database/Migrator.php';
317 187
318 -
319 -}
320 -add_action( 'template_redirect', 'easy_fonts_run_template_redirect', -999 );
188 + switch_to_blog( $blog_id );
189 + ( new \EasyFonts\Database\Migrator() )->install();
190 + restore_current_blog();
191 + },
192 + 10,
193 + 1
194 +);
321 195
322 -
323 -function easyfont_run_after_smart_slider( $buffer ) {
324 - if (get_option('easyfonts_host_google_fonts_locally_link', false)) {
325 - $buffer = easyfont_process_content_link_tag( $buffer );
196 +/**
197 + * Deactivation — clear scheduled events.
198 + */
199 +register_deactivation_hook(
200 + __FILE__,
201 + static function () {
202 + wp_clear_scheduled_hook( 'easyfonts_cleanup' );
326 203 }
327 - return $buffer;
328 -}
329 -
330 -function easyfonts_enqueue_options_styles() {
331 - if ( 'settings_page_easyfonts' == get_current_screen()->id ) {
332 - wp_enqueue_style( 'easyfonts-options-styles', plugin_dir_url( __FILE__ ) . 'assets/style.css', array(), '1.0.0' );
333 - }
334 -}
335 -add_action( 'admin_enqueue_scripts', 'easyfonts_enqueue_options_styles' );
336 -
337 -function easyfonts_settings_link($links) {
338 - $settings_link = '<a href="options-general.php?page=easyfonts">Settings</a>';
339 - array_push($links, $settings_link);
340 - return $links;
341 -}
342 -add_filter("plugin_action_links_easyfonts/easyfonts.php", "easyfonts_settings_link");
204 +);