PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.8.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.8.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.8.1, at includes/classes/font-load-locally.php

277 lines 7.2 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 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
22 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
23 error_log( print_r( $fonts, true ) );
24 }
25 foreach ( $fonts as $family => $weights ) {
26 if ( ! is_array( $weights ) ) {
27 continue;
28 }
29
30 // Only download missing weights
31 $weights_to_download = [];
32 foreach ( $weights as $weight ) {
33 if ( ! $this->is_font_weight_available( $family, $weight ) ) {
34 $weights_to_download[] = $weight;
35 }
36 }
37
38 if ( ! empty( $weights_to_download ) ) {
39 $this->download_font_family( $family, $weights_to_download );
40 }
41 }
42 }
43
44 /**
45 * Check if a specific font family and weight exists locally
46 *
47 * @param string $family
48 * @param string $weight
49 * @return bool
50 */
51 public function is_font_weight_available( $family, $weight ) {
52 $upload_dir = wp_upload_dir();
53 $folder = trailingslashit( $upload_dir['basedir'] ) . self::FONTS_FOLDER . '/' . $this->sanitize_family( $family );
54
55 if ( ! is_dir( $folder ) ) {
56 return false;
57 }
58
59 $weight_key = $weight === '400' || $weight === 'normal' ? '400' : $weight;
60 $files = glob( $folder . '/' . $weight_key . '-*.woff2' );
61
62 return ! empty( $files );
63 }
64
65 /**
66 * Download a Google Font family via CSS2 API
67 *
68 * @param string $family
69 * @param array $weights
70 */
71 public function download_font_family( $family, $weights ) {
72 $upload_dir = wp_upload_dir();
73 $base_dir = trailingslashit( $upload_dir['basedir'] ) . self::FONTS_FOLDER;
74 wp_mkdir_p( $base_dir );
75
76 $family_dir = $base_dir . '/' . $this->sanitize_family( $family );
77 wp_mkdir_p( $family_dir );
78
79 $css_url = $this->build_css_url( $family, $weights );
80 $woff2_list = $this->get_woff2_urls( $css_url );
81
82 foreach ( $woff2_list as $woff ) {
83 $weight = $woff['weight'];
84 $file_url = $woff['url'];
85 // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
86 $filename = $weight . '-' . basename( parse_url( $file_url, PHP_URL_PATH ) );
87 $filepath = $family_dir . '/' . $filename;
88
89 if ( file_exists( $filepath ) ) {
90 continue;
91 }
92
93 $response = wp_remote_get( $file_url, [ 'timeout' => 15 ] );
94
95 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
96 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
97 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
98 error_log( "Failed to download font file: {$file_url}" );
99 }
100 continue;
101 }
102
103 file_put_contents( $filepath, wp_remote_retrieve_body( $response ) );
104 }//end foreach
105 }
106
107 /**
108 * Build Google Fonts CSS2 API URL
109 *
110 * @param string $family
111 * @param array $weights
112 * @return string
113 */
114 public function build_css_url( $family, $weights ) {
115 $family_encoded = str_replace( ' ', '+', $family );
116
117 // Remove duplicates & sort weights
118 $weights = array_unique( array_map( 'strval', $weights ) );
119 sort( $weights );
120
121 // Correct Google Fonts format: wght@200;400;700;900
122 $weights_str = 'wght@' . implode( ';', $weights );
123
124 return "https://fonts.googleapis.com/css2?family={$family_encoded}:{$weights_str}&display=swap";
125 }
126
127
128 /**
129 * Extract .woff2 URLs and corresponding weight from Google Fonts CSS
130 *
131 * @param string $css_url
132 * @return array [['weight'=>'400','url'=>'https://...'], ...]
133 */
134 public function get_woff2_urls( $css_url ) {
135 $args = [
136 'timeout' => 15,
137 'headers' => [
138 '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'
139 ],
140 ];
141
142 $response = wp_remote_get( $css_url, $args );
143
144 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
145 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
146 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
147 error_log( 'Failed to fetch Google Fonts CSS: ' . $css_url );
148 }
149 return [];
150 }
151
152 $css = wp_remote_retrieve_body( $response );
153 $matches = [];
154 preg_match_all(
155 '/@font-face\s*{[^}]*font-weight:\s*(\d+);[^}]*src:\s*url\(([^)]+\.woff2)\)/i',
156 $css,
157 $matches,
158 PREG_SET_ORDER
159 );
160
161 $results = [];
162 foreach ( $matches as $m ) {
163 $results[] = [
164 'weight' => $m[1],
165 'url' => $m[2],
166 ];
167 }
168
169 return $results;
170 }
171
172 /**
173 * Enqueue local fonts if available, otherwise enqueue Google Fonts
174 *
175 * @param array $fonts
176 */
177 public function enqueue_fonts( $fonts ) {
178 if ( ! is_array( $fonts ) || empty( $fonts ) ) {
179 return;
180 }
181
182 $upload_dir = wp_upload_dir();
183 $css = '';
184
185 $missing_fonts = []; // <-- Store missing fonts here
186
187 foreach ( $fonts as $family => $weights ) {
188
189 $local_available = true;
190
191 // Check each requested weight
192 foreach ( $weights as $weight ) {
193 if ( ! $this->is_font_weight_available( $family, $weight ) ) {
194 $local_available = false;
195
196 // Store missing for Google Fonts fallback
197 if ( ! isset( $missing_fonts[ $family ] ) ) {
198 $missing_fonts[ $family ] = [];
199 }
200 $missing_fonts[ $family ][] = $weight;
201 }
202 }
203
204 // If local font exists, register @font-face rules
205 if ( $local_available ) {
206 $folder = trailingslashit( $upload_dir['baseurl'] ) .
207 self::FONTS_FOLDER . '/' . $this->sanitize_family( $family );
208
209 $files = glob(
210 trailingslashit( $upload_dir['basedir'] ) .
211 self::FONTS_FOLDER . '/' . $this->sanitize_family( $family ) . '/*.woff2'
212 );
213
214 foreach ( $files as $file_path ) {
215 $file_name = basename( $file_path );
216
217 if ( preg_match( '/^(\d+)-/', $file_name, $m ) ) {
218 $font_weight = $m[1];
219 } else {
220 $font_weight = '400';
221 }
222
223 $css .= "@font-face {
224 font-family: '{$family}';
225 font-style: normal;
226 font-weight: {$font_weight};
227 font-display: swap;
228 src: url('{$folder}/{$file_name}') format('woff2');
229 }";
230 }
231 }//end if
232 }//end foreach
233
234 /**
235 * 1. ENQUEUE LOCAL FONTS IF ANY FOUND
236 */
237 if ( ! empty( $css ) ) {
238 wp_register_style( 'ablocks-local-fonts', false );
239 wp_enqueue_style( 'ablocks-local-fonts' );
240 wp_add_inline_style( 'ablocks-local-fonts', AssetsGenerator::minify_css( $css ) );
241 }
242
243 /**
244 * 2. ENQUEUE ONLY MISSING GOOGLE FONTS
245 */
246 if ( ! empty( $missing_fonts ) ) {
247 $family_strings = [];
248
249 foreach ( $missing_fonts as $family => $weights ) {
250
251 // sanitize weights
252 $weights = array_unique( array_map( 'strval', $weights ) );
253 sort( $weights );
254
255 $family_encoded = str_replace( ' ', '+', $family );
256 $family_strings[] = "{$family_encoded}:wght@" . implode( ';', $weights );
257 }
258
259 $google_url = 'https://fonts.googleapis.com/css2?family=' .
260 implode( '&family=', $family_strings ) . '&display=swap';
261
262 wp_enqueue_style( 'ablocks-google-fonts', esc_url( $google_url ), [], null );
263 }
264 }
265
266
267 /**
268 * Sanitize font family for folder names
269 *
270 * @param string $family
271 * @return string
272 */
273 private function sanitize_family( $family ) {
274 return str_replace( ' ', '-', strtolower( $family ) );
275 }
276 }
277