PluginProbe ʕ •ᴥ•ʔ
Kubio AI Page Builder / 2.4.3
Kubio AI Page Builder v2.4.3
2.8.2 2.8.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.9.0 2.0.0 2.1.1 2.1.2 2.1.3 2.2.0 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.5 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.6.2 2.6.3 2.6.5 2.6.6 2.6.7 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0
kubio / lib / src / GoogleFontsLocalLoader.php
kubio / lib / src Last commit date
CLI 2 years ago Core 1 year ago DemoSites 1 year ago AssetsDependencyInjector.php 3 years ago Config.php 3 years ago FileLog.php 2 years ago Flags.php 2 years ago GoogleFontsLocalLoader.php 2 years ago GutenbergControls.php 1 year ago Migrations.php 4 years ago NotificationsManager.php 1 year ago PluginsManager.php 2 years ago
GoogleFontsLocalLoader.php
508 lines
1 <?php
2
3 namespace Kubio;
4
5 use IlluminateAgnostic\Arr\Support\Arr;
6 use Kubio\Core\StyleManager\Utils as StyleManagerUtils;
7 use Kubio\Core\Registry;
8 use Kubio\Core\Utils;
9
10 class GoogleFontsLocalLoader {
11
12 private static $instance = null;
13
14 private $queries_transient = 'kubio_local_google_queries_transient';
15 private $font_file_action = 'kubio_get_google_font_file';
16 private $fonts_css_action = 'kubio_get_google_font_css';
17
18 private $uploads_dir = 'kubio-google-fonts-cache';
19
20 private $google_css_url = 'https://fonts.googleapis.com/css';
21 private $google_font_url = 'https://fonts.gstatic.com/s';
22 private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0';
23
24 private $local_fonts_dir;
25 private $local_fonts_url;
26
27
28 /**
29 *
30 * @return GoogleFontsLocalLoader
31 */
32 public static function getInstance() {
33 if ( ! static::$instance ) {
34 static::$instance = new static();
35 }
36
37 return static::$instance;
38 }
39
40 public function __construct() {
41
42 $upload_dir = wp_upload_dir();
43 $upload_path = untrailingslashit( $upload_dir['basedir'] );
44
45 $this->local_fonts_dir = "{$upload_path}/{$this->uploads_dir}";
46 $this->local_fonts_url = "{$upload_dir['baseurl']}/{$this->uploads_dir}";
47
48 if ( ! file_exists( $this->local_fonts_dir ) ) {
49 mkdir( $this->local_fonts_dir, 0777, true );
50 }
51 }
52
53
54 public function resolveFontsCSS() {
55 $action = Arr::get( $_REQUEST, 'action' );
56
57 if ( $action !== $this->fonts_css_action ) {
58 return;
59 }
60
61 header( 'Content-type: text/css' );
62 header( 'Cache-control: public' );
63
64 $key = sanitize_key( Arr::get( $_REQUEST, 'key', '' ) );
65 $cached = $this->getCachedDataByKey( $key );
66
67 if ( ! $cached ) {
68 die( '' );
69 }
70
71 $query = Arr::get( $cached, 'query' );
72 $css = Arr::get( $cached, 'css' );
73
74 if ( ! $css ) {
75 $css = $this->getCSS( $query );
76 $this->cacheQueryCSS( $query, $css );
77 }
78
79 $css = $this->replacePlaceholdersWithLocalCSS( $css );
80
81 if ( Utils::isDebug() ) {
82 $css = "/* {$this->google_css_url}?family={$query} */\n\n{$css}";
83 }
84
85 die( $css );
86
87 }
88
89 private function getCSS( $query, $replace_urls = true ) {
90 $fonts_url = add_query_arg(
91 array(
92 'family' => urlencode( $query ),
93 'display' => 'swap',
94 ),
95 $this->google_css_url
96 );
97
98 $response = wp_remote_get(
99 $fonts_url,
100 array(
101 'user-agent' => $this->user_agent,
102 )
103 );
104
105 if ( is_wp_error( $response ) ) {
106 return null;
107 }
108
109 if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
110 return null;
111 }
112
113 $css = wp_remote_retrieve_body( $response );
114
115 if ( $replace_urls ) {
116 $css = $this->replaceGoogleURLS( $css );
117 }
118
119 return $css;
120 }
121
122 private function replaceGoogleURLS( $css ) {
123
124 $google_font_url = $this->google_font_url;
125 $css = preg_replace_callback(
126 '#url\((.*?)\)#',
127 function( $matches ) use ( $google_font_url ) {
128 $url = str_replace( $google_font_url, '', Arr::get( $matches, 1, '' ) );
129
130 return "url({{{{$url}}}})";
131 },
132 $css
133 );
134
135 return $css;
136 }
137
138 public function getLocalFontFilePath( $font_file ) {
139 $file_key = md5( $font_file );
140 return "{$this->local_fonts_dir}/{$file_key}.woff2";
141 }
142
143 public function getLocalFontFileURL( $font_file ) {
144 $file_key = md5( $font_file );
145 return "{$this->local_fonts_url}/{$file_key}.woff2";
146 }
147
148 public function saveFontContentToLocalFile( $font_file, $content ) {
149 $path = $this->getLocalFontFilePath( $font_file );
150 return file_put_contents( $path, $content );
151 }
152
153 public function localFontFileExists( $font_file ) {
154 return file_exists( $this->getLocalFontFilePath( $font_file ) );
155 }
156
157 private function replacePlaceholdersWithLocalCSS( $css ) {
158 $self = $this;
159 $action = $this->font_file_action;
160
161 $css = preg_replace_callback(
162 '#url\(\{\{\{(.*?)\}\}\}\)#',
163 function( $matches ) use ( $self, $action ) {
164 $font_file = Arr::get( $matches, 1, '' );
165
166 if ( $self->localFontFileExists( $font_file ) ) {
167
168 $url = $self->getLocalFontFileURL( $font_file );
169 } else {
170 $url = add_query_arg(
171 array(
172 'font' => urlencode( $font_file ),
173 'action' => $action,
174 'security' => $self->createSecurityKey( "{$action}_{$font_file}" ),
175 ),
176 admin_url( 'admin-ajax.php' )
177 );
178 }
179
180 return "url({$url})";
181 },
182 $css
183 );
184
185 return $css;
186
187 }
188
189 public function getCachedDataByKey( $key ) {
190 $transient = get_transient( $this->queries_transient );
191 if ( ! is_array( $transient ) ) {
192 return null;
193 }
194 return Arr::get( $transient, $key );
195 }
196
197 public function getCachedQueryData( $query ) {
198 return $this->getCachedDataByKey( md5( $query ) );
199 }
200
201 public function cacheQueryCSS( $query, $css ) {
202
203 $transient = get_transient( $this->queries_transient );
204 if ( ! is_array( $transient ) ) {
205 $transient = array();
206 }
207
208 Arr::set(
209 $transient,
210 md5( $query ),
211 array(
212 'query' => $query,
213 'css' => $css,
214 )
215 );
216
217 set_transient( $this->queries_transient, $transient );
218 }
219
220 public function addQueryToCache( $query ) {
221 $transient = get_transient( $this->queries_transient );
222 if ( ! is_array( $transient ) ) {
223 $transient = array();
224 }
225
226 $key = md5( $query );
227
228 if ( isset( $transient[ $key ] ) ) {
229 return;
230 }
231
232 Arr::set(
233 $transient,
234 $key,
235 array(
236 'query' => $query,
237 )
238 );
239 set_transient( $this->queries_transient, $transient );
240 }
241
242
243 public function enqueueFonts( $query ) {
244 $cached = $this->getCachedQueryData( $query );
245 if ( ! $cached ) {
246 $this->addQueryToCache(
247 $query,
248 array(
249 'query' => $query,
250 )
251 );
252 }
253
254 wp_enqueue_style(
255 'kubio-local-google-fonts',
256 add_query_arg(
257 array(
258 'action' => $this->fonts_css_action,
259 'key' => md5( $query ),
260 ),
261 site_url()
262 )
263 );
264 }
265
266 public function getFontsQuery( $withGeneralSettings = true ) {
267
268 $fonts = array();
269
270 if ( $withGeneralSettings ) {
271 // get global google fonts
272 $fonts = \kubio_get_global_data( 'fonts.google', array() );
273 }
274
275 // add current rendered fonts variants
276 $rendered_fonts = Registry::getInstance()->getRenderedFonts();
277 foreach ( $rendered_fonts as $family => $variants ) {
278 $fonts[] = array(
279 'family' => $family,
280 'variants' => $variants,
281 );
282 }
283
284 $fonts = apply_filters( 'kubio/google_fonts', $fonts );
285
286 if ( ! count( $fonts ) ) {
287 return null;
288 }
289
290 // merge fonts by family
291 $mapped_fonts = array();
292 foreach ( $fonts as $font_data ) {
293 $family = $font_data['family'];
294 $mapped_fonts[ $family ] = isset( $mapped_fonts[ $family ] ) ? $mapped_fonts[ $family ] : array();
295 $mapped_fonts[ $family ] = array_merge( $mapped_fonts[ $family ], $font_data['variants'] );
296
297 }
298
299 // build fonts query
300 $groups = array();
301 foreach ( $mapped_fonts as $family => $weights ) {
302
303 // add the default if necessary 400 and normailize weights array - ensure proper caching by sorting the weights and removing duplicates
304 $groups[] = $family . ':' . implode( ',', StyleManagerUtils::normalizeFontWeights( $weights ) );
305 }
306 $fonts_query = implode( '|', $groups );
307
308 return $fonts_query;
309 }
310
311 public function getFontsMap( $query, $subset = 'latin' ) {
312
313 $css = $this->getCSS( $query, false );
314
315 if ( $subset === 'all' ) {
316 $subset_regex = '/\/\*([^*\/]*)\*\//i';
317 preg_match_all( $subset_regex, $css, $matches, PREG_SET_ORDER );
318 $subsets_list = array();
319 foreach ( $matches as $match ) {
320 $current_subset = trim( $match[1] );
321 if ( ! in_array( $current_subset, $subsets_list ) ) {
322 $subsets_list[] = $current_subset;
323 }
324 }
325 $all_fonts = array();
326 foreach ( $subsets_list as $subset_item ) {
327 $fonts = $this->getFontsMap( $query, $subset_item );
328 $all_fonts = array_merge( $all_fonts, $fonts );
329 }
330
331 //sorts fonts faces
332 usort(
333 $all_fonts,
334 function( $a, $b ) {
335 return array( $a['font-family'], $a['font-style'], $a['font-weight'], $a['subset'] )
336 <=>
337 array( $b['font-family'], $b['font-style'], $b['font-weight'], $b['subset'] );
338 }
339 );
340 return $all_fonts;
341 }
342
343 // prepare subset
344 $css = preg_replace( '#/\*\s+?(' . $subset . ")\s+?\*/(.*\n?)@font-face#", 'is_subset_match', $css );
345
346 // remove comments
347 $css = preg_replace( '#/\*(.*?)\*/#', '', $css );
348 $css = preg_replace( '#format\((.*?)\)#', '', $css );
349 $css = preg_replace( '#url\(https://(.*?)\)#', '$1', $css );
350
351 $re = '/is_subset_match.*{\K[^}]*(?=})/';
352 preg_match_all( $re, $css, $matches, PREG_SET_ORDER );
353
354 $parsed = array();
355 $keys = array( 'font-family', 'src', 'font-style', 'font-weight', 'unicode-range' );
356 if ( $matches ) {
357
358 foreach ( $matches as $k => $ff ) {
359
360 $css = $ff[0];
361 $attrs = explode( ';', $css );
362
363 $props = array();
364 foreach ( $attrs as $attr ) {
365 if ( strlen( trim( $attr ) ) > 0 ) {
366 $pair = explode( ':', trim( $attr ) );
367 if ( in_array( $pair[0], $keys ) ) {
368 $value = trim( $pair[1] );
369
370 if ( $pair[0] === 'font-family' ) {
371 $value = str_replace( "'", '', $value );
372 }
373
374 if ( $pair[0] === 'font-weight' ) {
375 $value = intval( $value );
376 }
377
378 if ( $pair[0] === 'src' ) {
379 $value = "https://{$value}";
380 }
381
382 $props[ trim( $pair[0] ) ] = $value;
383 }
384 }
385 }
386 $props['subset'] = $subset;
387 $parsed[ $k ] = $props;
388 }
389 }
390
391 return $parsed;
392 }
393
394 public function resolveFont() {
395
396 $font_file = sanitize_text_field( Arr::get( $_REQUEST, 'font', '' ) );
397 $security_key = sanitize_text_field( Arr::get( $_REQUEST, 'security', '' ) );
398
399 $valid_nonce = $this->verifySecurityKey( $security_key, "{$this->font_file_action}_{$font_file}" );
400
401 if ( ! $valid_nonce ) {
402 wp_die( __( 'Frobidden', 'kubio' ), 403 );
403 }
404
405 $content = $this->resolveFontFileContent( $font_file );
406
407 if ( is_wp_error( $content ) ) {
408 wp_die( $content, 404 );
409 }
410
411 $this_year = strtotime( date( 'Y' ) . '-01-01' );
412 header( 'Content-type: font/woff2' );
413 header( 'Cache-control: public' );
414 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $this_year ) . ' GMT' );
415 header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', $this_year + YEAR_IN_SECONDS ) . ' GMT' );
416 header( 'Etag: ' . md5( base64_encode( $content ) ) );
417
418 die( $content );
419 }
420
421 private function resolveFontFileContent( $font_file ) {
422 if ( $this->localFontFileExists( $font_file ) ) {
423 return file_get_contents( $this->getLocalFontFilePath( $font_file ) );
424 }
425
426 if ( ! is_writable( $this->local_fonts_dir ) ) {
427 return new \WP_Error( 'folder_not_writable' );
428 }
429
430 $google_font_url = "{$this->google_font_url}/{$font_file}";
431
432 $reponse = wp_remote_get( $google_font_url );
433 if ( is_wp_error( $reponse ) ) {
434 return new \WP_Error( 'could_not_retrieve_url' );
435 }
436
437 $content = wp_remote_retrieve_body( $reponse );
438
439 $this->saveFontContentToLocalFile( $font_file, $content );
440
441 return $content;
442
443 }
444
445 private function getSecuritySalt() {
446 if ( defined( 'NONCE_KEY' ) ) {
447 return NONCE_KEY;
448 }
449
450 if ( define( 'SECURE_AUTH_KEY' ) ) {
451 return SECURE_AUTH_KEY;
452 }
453
454 if ( define( 'AUTH_KEY' ) ) {
455 return AUTH_KEY;
456 }
457
458 if ( define( 'SECURE_AUTH_SALT' ) ) {
459 return SECURE_AUTH_SALT;
460 }
461
462 if ( define( 'AUTH_SALT' ) ) {
463 return AUTH_SALT;
464 }
465
466 $pro_activation_time = Flags::get( 'kubio_pro_activation_time' );
467
468 if ( $pro_activation_time ) {
469 return $pro_activation_time;
470 }
471
472 $activation_time = Flags::get( 'kubio_activation_time' );
473
474 if ( $activation_time ) {
475 return $activation_time;
476 }
477
478 return uniqid( time() );
479 }
480
481 public function createSecurityKey( $action ) {
482 return wp_hash( $this->getSecuritySalt() . '|' . $action );
483 }
484
485 public function verifySecurityKey( $nonce, $action ) {
486 return $nonce === $this->createSecurityKey( $action );
487 }
488
489
490 public function addAdminAjaxActions() {
491 add_action( "wp_ajax_{$this->font_file_action}", array( $this, 'resolveFont' ) );
492 add_action( "wp_ajax_nopriv_{$this->font_file_action}", array( $this, 'resolveFont' ) );
493
494 add_action( 'plugins_loaded', array( $this, 'resolveFontsCSS' ) );
495
496 }
497
498 public static function enqueuLocalGoogleFonts( $fonts_query ) {
499 return GoogleFontsLocalLoader::getInstance()->enqueueFonts( $fonts_query );
500 }
501
502
503 public static function registerFontResolver() {
504 return GoogleFontsLocalLoader::getInstance()->addAdminAjaxActions();
505 }
506
507 }
508