PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.14.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.14.0
2.14.0 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 All 81 releases
← All changes | includes/classes/font-load-locally.php +328 -0 2.7.7 → 2.14.0 View file →
@@ -1,0 +1,328 @@
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 +}