| @@ -22,8 +22,9 @@ | ||
| 22 | 22 | class FontCollector { |
| 23 | 23 | |
| 24 | 24 | const POST_META_KEY = '_ablocks_fonts'; |
| 25 | 25 | const GLOBAL_OPTION_NAME = 'ablocks_global_fonts'; |
| 26 | + const SITE_OPTION_NAME = 'ablocks_site_fonts'; | |
| 26 | 27 | |
| 27 | 28 | /** |
| 28 | 29 | * Recursively collect [ family => [weights] ] from a list of parsed blocks. |
| 29 | 30 | * |
| @@ -75,9 +76,12 @@ | ||
| 75 | 76 | } |
| 76 | 77 | |
| 77 | 78 | if ( isset( $node['fontFamily'] ) && is_string( $node['fontFamily'] ) ) { |
| 78 | 79 | $family = trim( $node['fontFamily'] ); |
| 79 | - if ( '' !== $family && 'Default' !== $family ) { | |
| 80 | + // 'Default' sets no font and 'inherit' defers to the theme; neither is | |
| 81 | + // a downloadable family. A stored stack came from a custom value, so | |
| 82 | + // its files are the author's responsibility. | |
| 83 | + if ( '' !== $family && 'Default' !== $family && 'inherit' !== strtolower( $family ) && false === strpos( $family, ',' ) ) { | |
| 80 | 84 | $weight = '400'; |
| 81 | 85 | if ( isset( $node['weight'] ) && '' !== $node['weight'] && 'Default' !== $node['weight'] ) { |
| 82 | 86 | $weight = (string) $node['weight']; |
| 83 | 87 | } |
| @@ -206,14 +210,114 @@ | ||
| 206 | 210 | return is_array( $fonts ) ? $fonts : []; |
| 207 | 211 | } |
| 208 | 212 | |
| 209 | 213 | /* ------------------------------------------------------------------------- |
| 214 | + * Site-wide content (templates, template parts, theme builder layouts) | |
| 215 | + * ---------------------------------------------------------------------- */ | |
| 216 | + | |
| 217 | + /** | |
| 218 | + * Post types whose content is rendered outside the queried post: block theme | |
| 219 | + * templates and template parts (header/footer/archive markup) and aBlocks | |
| 220 | + * theme builder layouts. | |
| 221 | + * | |
| 222 | + * These are not `is_singular()` on the front end, so their fonts were never | |
| 223 | + * requested — a header built with aBlocks got `font-family: X` in CSS with no | |
| 224 | + * font-face rule and no Google stylesheet behind it. | |
| 225 | + * | |
| 226 | + * @return string[] | |
| 227 | + */ | |
| 228 | + public static function get_site_font_post_types() { | |
| 229 | + return (array) apply_filters( | |
| 230 | + 'ablocks/site_font_post_types', | |
| 231 | + [ 'wp_template', 'wp_template_part', 'ablocks_tb' ] | |
| 232 | + ); | |
| 233 | + } | |
| 234 | + | |
| 235 | + /** | |
| 236 | + * Collect fonts from every template / template part / theme builder layout. | |
| 237 | + * | |
| 238 | + * Which template WordPress will render is not known while `wp_head` runs, so | |
| 239 | + * the union of them is used. The result is cached in an option and only | |
| 240 | + * recomputed when one of those posts is saved, so the front end pays a single | |
| 241 | + * option read. Self-hosted faces are lazy — declaring one the page does not | |
| 242 | + * use costs a line of CSS, not a download. | |
| 243 | + */ | |
| 244 | + public static function collect_from_site_content() { | |
| 245 | + $fonts = []; | |
| 246 | + | |
| 247 | + $post_ids = get_posts( | |
| 248 | + [ | |
| 249 | + 'post_type' => self::get_site_font_post_types(), | |
| 250 | + 'post_status' => [ 'publish', 'draft', 'auto-draft', 'inherit' ], | |
| 251 | + 'posts_per_page' => 200, | |
| 252 | + 'fields' => 'ids', | |
| 253 | + 'no_found_rows' => true, | |
| 254 | + 'update_post_meta_cache' => false, | |
| 255 | + 'update_post_term_cache' => false, | |
| 256 | + 'suppress_filters' => true, | |
| 257 | + ] | |
| 258 | + ); | |
| 259 | + | |
| 260 | + foreach ( $post_ids as $post_id ) { | |
| 261 | + $fonts = self::merge( $fonts, self::collect_from_post( $post_id ) ); | |
| 262 | + } | |
| 263 | + | |
| 264 | + return $fonts; | |
| 265 | + } | |
| 266 | + | |
| 267 | + /** | |
| 268 | + * Recompute and store the site-wide font set, then self-host it. | |
| 269 | + */ | |
| 270 | + public static function update_site_fonts() { | |
| 271 | + $fonts = self::collect_from_site_content(); | |
| 272 | + update_option( self::SITE_OPTION_NAME, $fonts ); | |
| 273 | + self::download( $fonts ); | |
| 274 | + return $fonts; | |
| 275 | + } | |
| 276 | + | |
| 277 | + /** | |
| 278 | + * Get the site-wide font set, computing + caching on first access. | |
| 279 | + * | |
| 280 | + * Self-hosting downloads font files over the network, which must not happen | |
| 281 | + * inside a visitor's request. On the front end the set is collected and cached | |
| 282 | + * immediately (so the fonts do load, remotely) and the download is handed to a | |
| 283 | + * one-off cron event. | |
| 284 | + */ | |
| 285 | + public static function get_site_fonts() { | |
| 286 | + $fonts = get_option( self::SITE_OPTION_NAME, null ); | |
| 287 | + if ( is_array( $fonts ) ) { | |
| 288 | + return $fonts; | |
| 289 | + } | |
| 290 | + | |
| 291 | + if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) { | |
| 292 | + return self::update_site_fonts(); | |
| 293 | + } | |
| 294 | + | |
| 295 | + $fonts = self::collect_from_site_content(); | |
| 296 | + update_option( self::SITE_OPTION_NAME, $fonts ); | |
| 297 | + | |
| 298 | + if ( ! empty( $fonts ) && ! wp_next_scheduled( 'ablocks/download_site_fonts' ) ) { | |
| 299 | + wp_schedule_single_event( time() + 30, 'ablocks/download_site_fonts' ); | |
| 300 | + } | |
| 301 | + | |
| 302 | + return $fonts; | |
| 303 | + } | |
| 304 | + | |
| 305 | + /** | |
| 306 | + * Drop the cached site-wide font set (a template/part/layout changed). | |
| 307 | + */ | |
| 308 | + public static function flush_site_fonts() { | |
| 309 | + delete_option( self::SITE_OPTION_NAME ); | |
| 310 | + } | |
| 311 | + | |
| 312 | + /* ------------------------------------------------------------------------- | |
| 210 | 313 | * Frontend |
| 211 | 314 | * ---------------------------------------------------------------------- */ |
| 212 | 315 | |
| 213 | 316 | /** |
| 214 | - * The complete set of fonts the current request needs: global typography | |
| 215 | - * (every page) plus the current singular post's fonts. Cached per request. | |
| 317 | + * The complete set of fonts the current request needs: global typography and | |
| 318 | + * site-wide content (templates, parts, theme builder layouts) on every page, | |
| 319 | + * plus the queried post's own fonts. Cached per request. | |
| 216 | 320 | */ |
| 217 | 321 | public static function get_page_fonts() { |
| 218 | 322 | static $cache = null; |
| 219 | 323 | if ( null !== $cache ) { |
| @@ -219,9 +323,9 @@ | ||
| 219 | 323 | if ( null !== $cache ) { |
| 220 | 324 | return $cache; |
| 221 | 325 | } |
| 222 | 326 | |
| 223 | - $fonts = self::get_global_fonts(); | |
| 327 | + $fonts = self::merge( self::get_global_fonts(), self::get_site_fonts() ); | |
| 224 | 328 | |
| 225 | 329 | if ( is_singular() ) { |
| 226 | 330 | $post_id = get_queried_object_id(); |
| 227 | 331 | if ( $post_id ) { |
| @@ -228,9 +332,9 @@ | ||
| 228 | 332 | $fonts = self::merge( $fonts, self::get_post_fonts( $post_id ) ); |
| 229 | 333 | } |
| 230 | 334 | } |
| 231 | 335 | |
| 232 | - $cache = $fonts; | |
| 336 | + $cache = (array) apply_filters( 'ablocks/page_fonts', $fonts ); | |
| 233 | 337 | return $cache; |
| 234 | 338 | } |
| 235 | 339 | |
| 236 | 340 | /* ------------------------------------------------------------------------- |