PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.1
2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 All 80 releases
ablocks / includes / classes / font-load-locally.php

font-load-locally.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.13.1, at includes/classes/font-load-locally.php

329 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ABlocks\Classes;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 class FontLoadLocally {
9
10 const FONTS_FOLDER = 'ablocks-fonts';
11
12 /**
13 * Process a list of fonts and weights
14 *
15 * @param array $fonts Example: ["Roboto" => ["400","700"]]
16 */
17 public function process_font_queue( $fonts ) {
18 if ( ! is_array( $fonts ) || empty( $fonts ) ) {
19 return;
20 }
21 foreach ( $fonts as $family => $weights ) {
22 if ( ! is_array( $weights ) ) {
23 continue;
24 }
25
26 // Only download missing weights
27 $weights_to_download = [];
28 foreach ( $weights as $weight ) {
29 if ( ! $this->is_font_weight_available( $family, $weight ) ) {
30 $weights_to_download[] = $weight;
31 }
32 }
33
34 if ( ! empty( $weights_to_download ) ) {
35 $this->download_font_family( $family, $weights_to_download );
36 }
37 }
38 }
39
40 /**
41 * Check if a specific font family and weight exists locally
42 *
43 * @param string $family
44 * @param string $weight
45 * @return bool
46 */
47 public function is_font_weight_available( $family, $weight ) {
48 $upload_dir = wp_upload_dir();
49 $folder = trailingslashit( $upload_dir['basedir'] ) . self::FONTS_FOLDER . '/' . $this->sanitize_family( $family );
50
51 if ( ! is_dir( $folder ) ) {
52 return false;
53 }
54
55 $weight_key = $weight === '400' || $weight === 'normal' ? '400' : $weight;
56 $files = glob( $folder . '/' . $weight_key . '-*.woff2' );
57
58 return ! empty( $files );
59 }
60
61 /**
62 * Download a Google Font family via CSS2 API
63 *
64 * @param string $family
65 * @param array $weights
66 */
67 public function download_font_family( $family, $weights ) {
68 $upload_dir = wp_upload_dir();
69 $base_dir = trailingslashit( $upload_dir['basedir'] ) . self::FONTS_FOLDER;
70 wp_mkdir_p( $base_dir );
71
72 $family_dir = $base_dir . '/' . $this->sanitize_family( $family );
73 wp_mkdir_p( $family_dir );
74
75 $css_url = $this->build_css_url( $family, $weights );
76 $woff2_list = $this->get_woff2_urls( $css_url );
77
78 foreach ( $woff2_list as $woff ) {
79 $weight = $woff['weight'];
80 $file_url = $woff['url'];
81 // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
82 $filename = $weight . '-' . basename( parse_url( $file_url, PHP_URL_PATH ) );
83 $filepath = $family_dir . '/' . $filename;
84
85 if ( file_exists( $filepath ) ) {
86 continue;
87 }
88
89 $response = wp_remote_get( $file_url, [ 'timeout' => 15 ] );
90
91 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
92 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
93 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
94 error_log( "Failed to download font file: {$file_url}" );
95 }
96 continue;
97 }
98
99 file_put_contents( $filepath, wp_remote_retrieve_body( $response ) );
100 }//end foreach
101 }
102
103 /**
104 * Build Google Fonts CSS2 API URL
105 *
106 * @param string $family
107 * @param array $weights
108 * @return string
109 */
110 public function build_css_url( $family, $weights ) {
111 $family_encoded = str_replace( ' ', '+', $family );
112
113 // Remove duplicates & sort weights
114 $weights = array_unique( array_map( 'strval', $weights ) );
115 sort( $weights );
116
117 // Correct Google Fonts format: wght@200;400;700;900
118 $weights_str = 'wght@' . implode( ';', $weights );
119
120 return "https://fonts.googleapis.com/css2?family={$family_encoded}:{$weights_str}&display=swap";
121 }
122
123
124 /**
125 * Extract .woff2 URLs and corresponding weight from Google Fonts CSS
126 *
127 * @param string $css_url
128 * @return array [['weight'=>'400','url'=>'https://...'], ...]
129 */
130 public function get_woff2_urls( $css_url ) {
131 $args = [
132 'timeout' => 15,
133 'headers' => [
134 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
135 ],
136 ];
137
138 $response = wp_remote_get( $css_url, $args );
139
140 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
141 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
142 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
143 error_log( 'Failed to fetch Google Fonts CSS: ' . $css_url );
144 }
145 return [];
146 }
147
148 $css = wp_remote_retrieve_body( $response );
149 $matches = [];
150 preg_match_all(
151 '/@font-face\s*{[^}]*font-weight:\s*(\d+);[^}]*src:\s*url\(([^)]+\.woff2)\)/i',
152 $css,
153 $matches,
154 PREG_SET_ORDER
155 );
156
157 $results = [];
158 foreach ( $matches as $m ) {
159 $results[] = [
160 'weight' => $m[1],
161 'url' => $m[2],
162 ];
163 }
164
165 return $results;
166 }
167
168 /**
169 * Enqueue local fonts if available, otherwise enqueue Google Fonts
170 *
171 * @param array $fonts
172 */
173 public function enqueue_fonts( $fonts ) {
174 if ( ! is_array( $fonts ) || empty( $fonts ) ) {
175 return;
176 }
177
178 $upload_dir = wp_upload_dir();
179 $css = '';
180
181 $missing_fonts = []; // <-- Store missing fonts here
182
183 foreach ( $fonts as $family => $weights ) {
184
185 $local_available = true;
186
187 // Check each requested weight
188 foreach ( $weights as $weight ) {
189 if ( ! $this->is_font_weight_available( $family, $weight ) ) {
190 $local_available = false;
191
192 // Store missing for Google Fonts fallback
193 if ( ! isset( $missing_fonts[ $family ] ) ) {
194 $missing_fonts[ $family ] = [];
195 }
196 $missing_fonts[ $family ][] = $weight;
197 }
198 }
199
200 // If local font exists, register @font-face rules
201 if ( $local_available ) {
202 $folder = trailingslashit( $upload_dir['baseurl'] ) .
203 self::FONTS_FOLDER . '/' . $this->sanitize_family( $family );
204
205 $files = glob(
206 trailingslashit( $upload_dir['basedir'] ) .
207 self::FONTS_FOLDER . '/' . $this->sanitize_family( $family ) . '/*.woff2'
208 );
209
210 foreach ( $files as $file_path ) {
211 $file_name = basename( $file_path );
212
213 if ( preg_match( '/^(\d+)-/', $file_name, $m ) ) {
214 $font_weight = $m[1];
215 } else {
216 $font_weight = '400';
217 }
218
219 $css .= "@font-face {
220 font-family: '{$family}';
221 font-style: normal;
222 font-weight: {$font_weight};
223 font-display: swap;
224 src: url('{$folder}/{$file_name}') format('woff2');
225 }";
226 }
227 }//end if
228 }//end foreach
229
230 /**
231 * 1. ENQUEUE LOCAL FONTS IF ANY FOUND
232 */
233 if ( ! empty( $css ) ) {
234 wp_register_style( 'ablocks-local-fonts', false );
235 wp_enqueue_style( 'ablocks-local-fonts' );
236 wp_add_inline_style( 'ablocks-local-fonts', AssetsGenerator::minify_css( $css ) );
237 }
238
239 /**
240 * 2. ENQUEUE ONLY MISSING GOOGLE FONTS
241 */
242 if ( ! empty( $missing_fonts ) ) {
243 $family_strings = [];
244
245 foreach ( $missing_fonts as $family => $weights ) {
246
247 // sanitize weights
248 $weights = array_unique( array_map( 'strval', $weights ) );
249 sort( $weights );
250
251 $family_encoded = str_replace( ' ', '+', $family );
252 $family_strings[] = "{$family_encoded}:wght@" . implode( ';', $weights );
253 }
254
255 $google_url = 'https://fonts.googleapis.com/css2?family=' .
256 implode( '&family=', $family_strings ) . '&display=swap';
257
258 wp_enqueue_style( 'ablocks-google-fonts', esc_url( $google_url ), [], null );
259 }
260 }
261
262
263 /**
264 * Return locally-available @font-face descriptors for a family.
265 *
266 * @param string $family
267 * @param array $weights Requested weights.
268 * @return array [ [ 'weight' => '400', 'src' => 'https://.../400-xxx.woff2' ], ... ]
269 */
270 public function get_local_font_faces( $family, $weights = [] ) {
271 $upload_dir = wp_upload_dir();
272 $dir = trailingslashit( $upload_dir['basedir'] ) . self::FONTS_FOLDER . '/' . $this->sanitize_family( $family );
273 $url_base = trailingslashit( $upload_dir['baseurl'] ) . self::FONTS_FOLDER . '/' . $this->sanitize_family( $family );
274
275 if ( ! is_dir( $dir ) ) {
276 return [];
277 }
278
279 $faces = [];
280 foreach ( (array) glob( $dir . '/*.woff2' ) as $file_path ) {
281 $file_name = basename( $file_path );
282 $font_weight = preg_match( '/^(\d+)-/', $file_name, $m ) ? $m[1] : '400';
283
284 // If specific weights were requested, only include those.
285 if ( ! empty( $weights ) && ! in_array( (string) $font_weight, array_map( 'strval', $weights ), true ) ) {
286 continue;
287 }
288
289 $faces[] = [
290 'weight' => (string) $font_weight,
291 'src' => $url_base . '/' . $file_name,
292 ];
293 }
294
295 return $faces;
296 }
297
298 /**
299 * From a [family => weights] map, return the subset not available locally.
300 *
301 * @param array $fonts
302 * @return array [family => [missing weights]]
303 */
304 public function get_missing( $fonts ) {
305 $missing = [];
306 if ( ! is_array( $fonts ) ) {
307 return $missing;
308 }
309 foreach ( $fonts as $family => $weights ) {
310 foreach ( (array) $weights as $weight ) {
311 if ( ! $this->is_font_weight_available( $family, $weight ) ) {
312 $missing[ $family ][] = (string) $weight;
313 }
314 }
315 }
316 return $missing;
317 }
318
319 /**
320 * Sanitize font family for folder names
321 *
322 * @param string $family
323 * @return string
324 */
325 public function sanitize_family( $family ) {
326 return str_replace( ' ', '-', strtolower( $family ) );
327 }
328 }
329