PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.4
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.4
4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 All 202 releases
betterdocs / includes / Modules / StyleHandler.php

StyleHandler.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.4, at includes/Modules/StyleHandler.php

608 lines 26.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPDeveloper\BetterDocs\Modules;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8
9
10 // Doc/category-listing primitives (meta_query/tax_query, post__not_in/exclude)
11 // are intrinsic to BetterDocs' KB / category / FAQ filters and intentional.
12 // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
13 // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
14 // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_tax_query
15 // phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
16 // phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
17 use WPDeveloper\BetterDocs\Utils\CSSParser;
18
19 final class StyleHandler {
20 private static $instance;
21
22 private $prefix = 'betterdocs-style';
23 private $style_dir;
24 private $style_url;
25 /**
26 * Holds block styles array
27 *
28 * @var array
29 */
30 public static $_block_styles = array( );
31
32 /**
33 * Post IDs of FSE templates / template parts whose CSS was generated
34 * during the current request. Used to enqueue the matching per-template
35 * CSS files on the frontend.
36 *
37 * @var int[]
38 */
39 public static $_fse_template_post_ids = array( );
40
41 public static function init() {
42 if ( null === self::$instance ) {
43 self::$instance = new self();
44 }
45
46 return self::$instance;
47 }
48
49 public function __construct() {
50 $upload_dir = wp_upload_dir();
51
52 $this->style_dir = $upload_dir[ 'basedir' ] . '/' . $this->prefix . DIRECTORY_SEPARATOR;
53 $this->style_url = set_url_scheme( $upload_dir[ 'baseurl' ] ) . '/' . $this->prefix . '/';
54
55 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ), 99 );
56 add_action( 'save_post', array( $this, 'on_save_post' ), 10, 3 );
57 add_action( 'wp', array( $this, 'generate_post_content' ) );
58
59 // Synced patterns in block widgets render on every page; track them
60 // once the widget/sidebar changes of the request are all saved.
61 add_action( 'rest_after_save_widget', array( $this, 'schedule_widget_patterns_refresh' ) );
62 add_action( 'rest_delete_widget', array( $this, 'schedule_widget_patterns_refresh' ) );
63 add_action( 'rest_save_sidebar', array( $this, 'schedule_widget_patterns_refresh' ) );
64 add_action( 'after_switch_theme', array( $this, 'schedule_widget_patterns_refresh' ) );
65 add_action( 'rest_after_save_widget', array( $this, 'after_save_widget' ), 10, 4 );
66 add_action( 'rest_delete_widget', array( $this, 'after_save_widget' ) );
67 add_action( 'rest_save_sidebar', array( $this, 'after_save_widget' ) );
68 add_action( 'after_switch_theme', array( $this, 'after_save_widget' ) );
69
70 // FSE assets generation
71 add_action(
72 'init',
73 function () {
74 if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
75 add_filter( '404_template', array( $this, 'fse_assets_generation' ), 99, 3 );
76 add_filter( 'archive_template', array( $this, 'fse_assets_generation' ), 99, 3 );
77 add_filter( 'category_template', array( $this, 'fse_assets_generation' ), 99, 3 );
78 add_filter( 'frontpage_template', array( $this, 'fse_assets_generation' ), 99, 3 );
79 add_filter( 'home_template', array( $this, 'fse_assets_generation' ), 99, 3 );
80 add_filter( 'index_template', array( $this, 'fse_assets_generation' ), 99, 3 );
81 add_filter( 'page_template', array( $this, 'fse_assets_generation' ), 99, 3 );
82 add_filter( 'search_template', array( $this, 'fse_assets_generation' ), 99, 3 );
83 add_filter( 'single_template', array( $this, 'fse_assets_generation' ), 99, 3 );
84 add_filter( 'singular_template', array( $this, 'fse_assets_generation' ), 99, 3 );
85 add_filter( 'tag_template', array( $this, 'fse_assets_generation' ), 99, 3 );
86 add_filter( 'taxonomy_template', array( $this, 'fse_assets_generation' ), 99, 3 );
87 }
88 },
89 999
90 );
91 }
92
93 /**
94 * Generate FSE Assets
95 */
96 public function fse_assets_generation( $template, $type, $templates ) {
97 $block_template = resolve_block_template( $type, $templates, $template );
98 if ( ! empty( $block_template ) ) {
99 $parsed_content = parse_blocks( $block_template->content );
100 if ( is_array( $parsed_content ) && ! empty( $parsed_content ) ) {
101 foreach ( $parsed_content as $content ) {
102 if ( ( 'core/template-part' == $content[ 'blockName' ] ) || ( 'core/template' == $content[ 'blockName' ] ) ) {
103 $post_ids = isset( $content[ 'attrs' ][ 'slug' ] ) ? self::betterdocs_get_post_content_by_post_name( $content[ 'attrs' ][ 'slug' ] ) : array( );
104
105 if ( ! empty( $post_ids ) ) {
106 foreach ( $post_ids as $id ) {
107 $post_id = (int) $id[ 'ID' ];
108 self::$_fse_template_post_ids[ ] = $post_id;
109 $post = get_post( $post_id );
110 $parsed_content = parse_blocks( $post->post_content );
111 $this->write_css_from_content( $post, $post_id, $parsed_content );
112 }
113 }
114 } else {
115 $post_ids = self::betterdocs_get_post_content_by_post_name( $block_template->slug );
116 if ( ! empty( $post_ids ) ) {
117 foreach ( $post_ids as $id ) {
118 $post_id = (int) $id[ 'ID' ];
119 self::$_fse_template_post_ids[ ] = $post_id;
120 $post = get_post( $post_id );
121 $parsed_content = parse_blocks( $post->post_content );
122 $this->write_css_from_content( $post, $post_id, $parsed_content );
123 }
124 }
125 }
126 }
127 }
128 }
129
130 return $template;
131 }
132
133 /**
134 * Write CSS
135 */
136 public function write_css_from_content( $post, $post_id, $parsed_content ) {
137 $betterdocs_blocks = array( );
138 $recursive_response = CSSParser::betterdocs_block_style_recursive( $parsed_content, $betterdocs_blocks );
139 $all_reusable_blocks = ! empty( $recursive_response[ 'reusableBlocks' ] ) ? $recursive_response[ 'reusableBlocks' ] : array( );
140 // remove empty reusable blocks
141 $reusable_Blocks = array_filter(
142 $all_reusable_blocks,
143 function ( $v ) {
144 return ! empty( $v );
145 }
146 );
147 unset( $recursive_response[ 'reusableBlocks' ] );
148 $style = CSSParser::blocks_to_style_array( $recursive_response );
149 $reusableIds = $reusable_Blocks ? array_keys( $reusable_Blocks ) : array( );
150 // The option is merged into every page's enqueue, so only FSE
151 // templates / template parts (rendered around every page) may set
152 // it. Ordinary posts keep their ids in post meta below; they used to
153 // overwrite the option too, so whatever the last post with synced
154 // patterns referenced was loaded site-wide.
155 if ( ! empty( $reusableIds ) && $this->is_fse_template( $post ) ) {
156 update_option( '_betterdocs_reusable_block_ids', $reusableIds );
157 }
158 update_post_meta( $post_id, '_betterdocs_reusable_block_ids', $reusableIds );
159 $this->write_block_css( $style, $post ); //Write CSS file for this page
160
161 // Every referenced reusable block, including those with no BetterDocs
162 // styles, so their stale files (e.g. pre-#174 copies of Essential
163 // Blocks' CSS) are removed.
164 foreach ( $all_reusable_blocks as $blockId => $block ) {
165 $style = CSSParser::blocks_to_style_array( $block );
166 $this->write_reusable_block_css( $style, $blockId );
167 }
168 }
169
170 /**
171 * Whether a post is an FSE template or template part.
172 *
173 * @param \WP_Post|null $post Post.
174 * @return bool
175 */
176 private function is_fse_template( $post ) {
177 return isset( $post->post_type ) && ( 'wp_template_part' === $post->post_type || 'wp_template' === $post->post_type );
178 }
179
180 /**
181 * Rebuild the widget CSS when a widget or sidebar is saved or a widget is
182 * deleted — once, at the end of the request: core fires
183 * rest_after_save_widget before it assigns the widget to its sidebar.
184 * @return void
185 * @since 3.5.3
186 */
187 public function after_save_widget() {
188 if ( false === has_action( 'shutdown', array( $this, 'rebuild_widget_css' ) ) ) {
189 add_action( 'shutdown', array( $this, 'rebuild_widget_css' ) );
190 }
191 }
192
193 /**
194 * Write the widget CSS file from every active block widget's BetterDocs
195 * blocks, or remove it when there are none. It used to hold only the
196 * widget saved last, and was never removed (e.g. stale Essential Blocks
197 * CSS collected before #174 stayed loaded on every page).
198 * @return void
199 */
200 public function rebuild_widget_css() {
201 $styles = array( );
202 foreach ( $this->active_block_widget_contents() as $content ) {
203 if ( false === strpos( $content, '<!-- wp:betterdocs/' ) ) {
204 continue;
205 }
206 $betterdocs_blocks = array( );
207 $recursive_response = CSSParser::betterdocs_block_style_recursive( parse_blocks( $content ), $betterdocs_blocks );
208 unset( $recursive_response[ 'reusableBlocks' ] );
209 $styles = array_merge( $styles, CSSParser::blocks_to_style_array( $recursive_response ) );
210 }
211
212 $file = $this->style_dir . $this->prefix . '-widget.min.css';
213 $css = empty( $styles ) ? '' : CSSParser::build_css( $styles );
214 if ( empty( $css ) ) {
215 if ( file_exists( $file ) ) {
216 wp_delete_file( $file );
217 }
218 return;
219 }
220
221 // Unchanged CSS: leave the file (and its mtime-based version) alone.
222 if ( file_exists( $file ) && file_get_contents( $file ) === $css ) {
223 return;
224 }
225
226 if ( ! file_exists( $this->style_dir ) ) {
227 wp_mkdir_p( $this->style_dir );
228 }
229 file_put_contents( $file, $css );
230 }
231
232 /**
233 * Content of the block widgets placed in a sidebar (not inactive).
234 * @return string[]
235 */
236 private function active_block_widget_contents() {
237 $contents = array( );
238 $instances = get_option( 'widget_block', array( ) );
239 foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
240 if ( 'wp_inactive_widgets' === $sidebar_id || ! is_array( $widget_ids ) ) {
241 continue;
242 }
243 foreach ( $widget_ids as $widget_id ) {
244 if ( preg_match( '/^block-(\d+)$/', (string) $widget_id, $matches ) && ! empty( $instances[ $matches[1] ]['content'] ) ) {
245 $contents[] = (string) $instances[ $matches[1] ]['content'];
246 }
247 }
248 }
249 return $contents;
250 }
251
252 /**
253 * Load Dependencies
254 */
255 private function load_style_handler_dependencies() {
256 require_once plugin_dir_path( __FILE__ ) . 'includes/class-parse-css.php';
257 }
258
259 /**
260 * Enqueue frontend css for post if have one
261 * @return void
262 * @since 1.0.2
263 */
264 public function enqueue_frontend_assets() {
265 global $post;
266 $deps = apply_filters( 'betterdocs_generated_css_frontend_deps', array( ) );
267
268 // generatepress elements
269 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WP core 'active_plugins' filter, applied to detect GeneratePress Premium presence on multisite.
270 if ( in_array( 'gp-premium/gp-premium.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
271 $gp_elements = get_posts( array( 'post_type' => 'gp_elements' ) );
272 if ( is_array( $gp_elements ) && ! empty( $gp_elements ) ) {
273 foreach ( $gp_elements as $element ) {
274 if ( file_exists( $this->style_dir . $this->prefix . '-' . $element->ID . '.min.css' ) ) {
275 wp_enqueue_style( 'betterdocs-block-style-' . $element->ID, $this->style_url . $this->prefix . '-' . $element->ID . '.min.css', $deps, $this->file_version( $this->style_dir . $this->prefix . '-' . $element->ID . '.min.css' ) );
276 }
277 }
278 }
279 }
280
281 if ( ! empty( $post ) && ! empty( $post->ID ) ) {
282 //Page/Post Style Enqueue — only for posts that hold BetterDocs
283 // blocks. Until #174 the parser also collected Essential Blocks'
284 // blocks, so files written then for pages without BetterDocs
285 // blocks are stale duplicates of Essential Blocks' CSS; they are
286 // skipped here and removed on the post's next save (#174).
287 $post_css = $this->style_dir . $this->prefix . '-' . $post->ID . '.min.css';
288 if ( file_exists( $post_css ) && false !== strpos( (string) $post->post_content, '<!-- wp:betterdocs/' ) ) {
289 wp_enqueue_style( 'betterdocs-block-style-' . $post->ID, $this->style_url . $this->prefix . '-' . $post->ID . '.min.css', $deps, $this->file_version( $post_css ) );
290 }
291
292 // Reusable block Style Enqueues
293 $reusableIds = get_post_meta( $post->ID, '_betterdocs_reusable_block_ids', true );
294 $reusableIds = ! empty( $reusableIds ) ? $reusableIds : array( );
295 $templateReusableIds = get_option( '_betterdocs_reusable_block_ids', array( ) );
296 $reusableIds = array_unique( array_merge( $reusableIds, $templateReusableIds ) );
297 foreach ( $reusableIds as $reusableId ) {
298 $this->enqueue_reusable_block_style( $reusableId, $deps );
299 }
300 }
301
302 //Widget Style Enqueue — only while an active block widget holds
303 // BetterDocs blocks, so a stale file (see rebuild_widget_css()) is
304 // not loaded site-wide until the next widget save removes it.
305 if ( file_exists( $this->style_dir . $this->prefix . '-widget.min.css' ) && false !== strpos( implode( '', $this->active_block_widget_contents() ), '<!-- wp:betterdocs/' ) ) {
306 wp_enqueue_style( 'betterdocs-widget-style', $this->style_url . $this->prefix . '-widget.min.css', $deps, $this->file_version( $this->style_dir . $this->prefix . '-widget.min.css' ) );
307 }
308
309 // Synced patterns in block widgets, on every page. Built once on
310 // sites whose widgets were saved before this list existed.
311 $widgetReusableIds = get_option( '_betterdocs_widget_reusable_block_ids', null );
312 if ( null === $widgetReusableIds ) {
313 $widgetReusableIds = $this->refresh_widget_patterns();
314 }
315 foreach ( (array) $widgetReusableIds as $reusableId ) {
316 $this->enqueue_reusable_block_style( $reusableId, $deps );
317 }
318
319 //FSE Style Enqueue — one file per FSE template/template-part post so
320 // styles do not bleed across templates (issue #53).
321 if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() && betterdocs()->helper->is_templates() && ! empty( self::$_fse_template_post_ids ) ) {
322 foreach ( array_unique( self::$_fse_template_post_ids ) as $template_post_id ) {
323 $file_name = $this->prefix . '-edit-site-' . abs( $template_post_id ) . '.min.css';
324 if ( file_exists( $this->style_dir . $file_name ) ) {
325 wp_enqueue_style(
326 'betterdocs-fullsite-style-' . abs( $template_post_id ),
327 $this->style_url . $file_name,
328 $deps,
329 $this->file_version( $this->style_dir . $file_name )
330 );
331 }
332 }
333 }
334
335 /**
336 * Hooks assets for enqueue in frontend
337 *
338 * @param $path string
339 * @param $url string
340 *
341 * @since 3.0.0
342 */
343 do_action( 'betterdocs_frontend_assets', $this->style_dir, $this->style_url );
344 }
345
346 /**
347 * Get post content when page is saved
348 */
349 public function on_save_post( $post_id, $post, $update ) {
350 $post_type = get_post_type( $post_id );
351
352 //If This page is draft, return
353 if ( isset( $post->post_status ) && 'auto-draft' == $post->post_status ) {
354 return;
355 }
356
357 // Autosave, do nothing
358 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
359 return;
360 }
361
362 // Return if it's a post revision
363 if ( false !== wp_is_post_revision( $post_id ) ) {
364 return;
365 }
366
367 $parsed_content = $this->get_parsed_content( $post_id, $post, $post_type );
368
369 // A synced pattern is styled through its reusable-block file, which
370 // pages referencing it enqueue. Refresh (or remove) that file here so
371 // edits to the pattern take effect without re-saving those pages.
372 if ( 'wp_block' === $post_type ) {
373 $betterdocs_blocks = array( );
374 $recursive_response = is_array( $parsed_content ) ? CSSParser::betterdocs_block_style_recursive( $parsed_content, $betterdocs_blocks ) : array( );
375 $this->write_reusable_block_css( CSSParser::blocks_to_style_array( $recursive_response ), $post_id );
376 return;
377 }
378
379 if ( is_array( $parsed_content ) && ! empty( $parsed_content ) ) {
380 $this->write_css_from_content( $post, $post_id, $parsed_content );
381 }
382 }
383
384 private function get_parsed_content( $post_id, $post, $post_type ) {
385 if ( 'wp_template_part' === $post_type || 'wp_template' === $post_type ) {
386 $post = get_post( $post_id );
387 }
388
389 if ( ! $post ) {
390 return array( );
391 }
392
393 $parsed_content = parse_blocks( $post->post_content );
394
395 if ( empty( $parsed_content ) ) {
396 delete_post_meta( $post_id, '_betterdocs_reusable_block_ids' );
397 }
398
399 return $parsed_content;
400 }
401
402 /**
403 * Get post content when page is load in frontend
404 */
405 /**
406 * Cheap pre-check: can this post's content hold BetterDocs blocks,
407 * directly or through a reusable block (`core/block` ref)?
408 *
409 * @param \WP_Post|null $post Post.
410 * @return bool
411 */
412 public static function may_have_betterdocs_blocks( $post ) {
413 $content = isset( $post->post_content ) ? (string) $post->post_content : '';
414 return false !== strpos( $content, '<!-- wp:betterdocs/' ) || false !== strpos( $content, '<!-- wp:block ' );
415 }
416
417 /**
418 * Whether a synced pattern (wp_block) holds BetterDocs blocks.
419 * get_post() is object-cached, so this is cheap per enqueue.
420 *
421 * @param int $id wp_block post ID.
422 * @return bool
423 */
424 private function reusable_block_has_betterdocs_blocks( $id ) {
425 $block = get_post( (int) $id );
426 return isset( $block->post_content ) && false !== strpos( (string) $block->post_content, '<!-- wp:betterdocs/' );
427 }
428
429 /**
430 * Refresh the widget pattern list at the end of the request: core fires
431 * rest_after_save_widget before it assigns the widget to its sidebar.
432 * @return void
433 */
434 public function schedule_widget_patterns_refresh() {
435 if ( false === has_action( 'shutdown', array( $this, 'refresh_widget_patterns' ) ) ) {
436 add_action( 'shutdown', array( $this, 'refresh_widget_patterns' ) );
437 }
438 }
439
440 /**
441 * Write the CSS of the synced patterns placed in active block widgets and
442 * record their ids, which every page enqueues.
443 * @return array Pattern ids.
444 */
445 public function refresh_widget_patterns() {
446 $ids = array( );
447 $instances = get_option( 'widget_block', array( ) );
448 foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
449 if ( 'wp_inactive_widgets' === $sidebar_id || ! is_array( $widget_ids ) ) {
450 continue;
451 }
452 foreach ( $widget_ids as $widget_id ) {
453 if ( ! preg_match( '/^block-(\d+)$/', (string) $widget_id, $matches ) || empty( $instances[ $matches[1] ]['content'] ) ) {
454 continue;
455 }
456 $betterdocs_blocks = array( );
457 $recursive_response = CSSParser::betterdocs_block_style_recursive( parse_blocks( $instances[ $matches[1] ]['content'] ), $betterdocs_blocks );
458 $reusable_blocks = ! empty( $recursive_response['reusableBlocks'] ) ? $recursive_response['reusableBlocks'] : array( );
459 foreach ( $reusable_blocks as $block_id => $block ) {
460 $this->write_reusable_block_css( CSSParser::blocks_to_style_array( $block ), $block_id );
461 if ( ! empty( $block ) ) {
462 $ids[] = (int) $block_id;
463 }
464 }
465 }
466 }
467
468 $ids = array_values( array_unique( $ids ) );
469 update_option( '_betterdocs_widget_reusable_block_ids', $ids );
470 return $ids;
471 }
472
473 /**
474 * Enqueue a synced pattern's CSS file, if it holds BetterDocs blocks.
475 * @return void
476 */
477 private function enqueue_reusable_block_style( $reusableId, $deps ) {
478 // Only for synced patterns that hold BetterDocs blocks: files written
479 // before #174 for patterns built with other plugins' blocks are stale
480 // duplicates of their CSS (#174).
481 $file = $this->style_dir . 'reusable-blocks/betterdocs-reusable-' . $reusableId . '.min.css';
482 if ( file_exists( $file ) && $this->reusable_block_has_betterdocs_blocks( $reusableId ) ) {
483 wp_enqueue_style( 'betterdocs-reusable-block-style-' . $reusableId, $this->style_url . 'reusable-blocks/betterdocs-reusable-' . $reusableId . '.min.css', $deps, $this->file_version( $file ) );
484 }
485 }
486
487 public function generate_post_content() {
488 $post_id = get_the_ID();
489 if ( $post_id ) {
490 $post_type = get_post_type( $post_id );
491 $post = get_post( $post_id );
492 //If This page is draft, return
493 if ( isset( $post->post_status ) && 'auto-draft' == $post->post_status ) {
494 return;
495 }
496
497 // Return if it's a post revision
498 if ( false !== wp_is_post_revision( $post_id ) ) {
499 return null;
500 }
501
502 // This runs on every uncached front-end request. Skip the parse
503 // and the file/meta/option writes for content with nothing for
504 // BetterDocs to style — on a large page built with other blocks
505 // that was a full parse_blocks() plus a CSS rewrite per view.
506 if ( ! self::may_have_betterdocs_blocks( $post ) ) {
507 return;
508 }
509
510 $parsed_content = $this->get_parsed_content( $post_id, $post, $post_type );
511
512 if ( is_array( $parsed_content ) && ! empty( $parsed_content ) ) {
513 $this->write_css_from_content( $post, $post_id, $parsed_content );
514 }
515 }
516 }
517
518 /**
519 * Cache-busting version for a generated stylesheet: its modification time.
520 * Was a random value per render, which stopped browsers caching the file.
521 *
522 * @param string $path File path.
523 * @return string|false
524 */
525 private function file_version( $path ) {
526 $mtime = @filemtime( $path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
527 return $mtime ? (string) $mtime : false;
528 }
529
530 /**
531 * Ajax callback to write css in upload directory
532 * @retun void
533 * @since 1.0.2
534 */
535 private function write_block_css( $block_styles, $post ) {
536 // Write CSS for FSE templates / template parts — one file per post so
537 // styles from different templates do not overwrite each other in a
538 // shared file (issue #53).
539 if ( isset( $post->post_type ) && ( 'wp_template_part' === $post->post_type || 'wp_template' === $post->post_type ) ) {
540 $file = $this->style_dir . $this->prefix . '-edit-site-' . abs( $post->ID ) . '.min.css';
541 } else { // Page/Posts
542 $file = $this->style_dir . $this->prefix . '-' . abs( $post->ID ) . '.min.css';
543 }
544
545 $css = empty( $block_styles ) ? '' : CSSParser::build_css( $block_styles );
546 if ( empty( $css ) ) {
547 // No BetterDocs blocks left: drop the file so a stale one is not
548 // enqueued forever (#174).
549 if ( file_exists( $file ) ) {
550 wp_delete_file( $file );
551 }
552 return;
553 }
554
555 // Unchanged CSS: leave the file (and its mtime-based version) alone,
556 // so browsers keep their cached copy.
557 if ( file_exists( $file ) && file_get_contents( $file ) === $css ) {
558 return;
559 }
560
561 if ( ! file_exists( $this->style_dir ) ) {
562 wp_mkdir_p( $this->style_dir );
563 }
564 file_put_contents( $file, $css );
565 }
566
567 /**
568 * Write css for Reusable block
569 * @retun void
570 * @since 3.4.0
571 */
572 private function write_reusable_block_css( $block_styles, $id ) {
573 $upload_dir = $this->style_dir . 'reusable-blocks/';
574 $file = $upload_dir . 'betterdocs-reusable-' . abs( $id ) . '.min.css';
575
576 $css = empty( $block_styles ) || ! is_array( $block_styles ) ? '' : CSSParser::build_css( $block_styles );
577 if ( empty( $css ) ) {
578 // No BetterDocs blocks in this synced pattern: drop the file so a
579 // stale one is not enqueued forever (#174).
580 if ( file_exists( $file ) ) {
581 wp_delete_file( $file );
582 }
583 return;
584 }
585
586 // Unchanged CSS: leave the file (and its mtime-based version) alone.
587 if ( file_exists( $file ) && file_get_contents( $file ) === $css ) {
588 return;
589 }
590
591 if ( ! file_exists( $upload_dir ) ) {
592 wp_mkdir_p( $upload_dir );
593 }
594 file_put_contents( $file, $css );
595 }
596
597 /**
598 * Get post id by post_name for template
599 */
600 public static function betterdocs_get_post_content_by_post_name( $post_name ) {
601 global $wpdb;
602 $sql = $wpdb->prepare( "SELECT ID FROM {$wpdb->prefix}posts WHERE post_name = %s", $post_name );
603
604 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off lookup of FSE template posts by post_name during CSS generation; no caching layer applies.
605 return $wpdb->get_results( $sql, ARRAY_A );
606 }
607 }
608