PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.5.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.5.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 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Modules / StyleHandler.php

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

412 lines 17.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Modules;
4
5 use WPDeveloper\BetterDocs\Utils\CSSParser;
6
7 final class StyleHandler {
8 private static $instance;
9
10 private $prefix = 'betterdocs-style';
11 private $style_dir;
12 private $style_url;
13 /**
14 * Holds block styles array
15 *
16 * @var array
17 */
18 public static $_block_styles = array( );
19
20 /**
21 * Post IDs of FSE templates / template parts whose CSS was generated
22 * during the current request. Used to enqueue the matching per-template
23 * CSS files on the frontend.
24 *
25 * @var int[]
26 */
27 public static $_fse_template_post_ids = array( );
28
29 public static function init() {
30 if ( null === self::$instance ) {
31 self::$instance = new self();
32 }
33
34 return self::$instance;
35 }
36
37 public function __construct() {
38 $upload_dir = wp_upload_dir();
39
40 $this->style_dir = $upload_dir[ 'basedir' ] . '/' . $this->prefix . DIRECTORY_SEPARATOR;
41 $this->style_url = set_url_scheme( $upload_dir[ 'baseurl' ] ) . '/' . $this->prefix . '/';
42
43 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ), 99 );
44 add_action( 'save_post', array( $this, 'on_save_post' ), 10, 3 );
45 add_action( 'wp', array( $this, 'generate_post_content' ) );
46 add_action( 'rest_after_save_widget', array( $this, 'after_save_widget' ), 10, 4 );
47
48 // FSE assets generation
49 add_action(
50 'init',
51 function () {
52 if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
53 add_filter( '404_template', array( $this, 'fse_assets_generation' ), 99, 3 );
54 add_filter( 'archive_template', array( $this, 'fse_assets_generation' ), 99, 3 );
55 add_filter( 'category_template', array( $this, 'fse_assets_generation' ), 99, 3 );
56 add_filter( 'frontpage_template', array( $this, 'fse_assets_generation' ), 99, 3 );
57 add_filter( 'home_template', array( $this, 'fse_assets_generation' ), 99, 3 );
58 add_filter( 'index_template', array( $this, 'fse_assets_generation' ), 99, 3 );
59 add_filter( 'page_template', array( $this, 'fse_assets_generation' ), 99, 3 );
60 add_filter( 'search_template', array( $this, 'fse_assets_generation' ), 99, 3 );
61 add_filter( 'single_template', array( $this, 'fse_assets_generation' ), 99, 3 );
62 add_filter( 'singular_template', array( $this, 'fse_assets_generation' ), 99, 3 );
63 add_filter( 'tag_template', array( $this, 'fse_assets_generation' ), 99, 3 );
64 add_filter( 'taxonomy_template', array( $this, 'fse_assets_generation' ), 99, 3 );
65 }
66 },
67 999
68 );
69 }
70
71 /**
72 * Generate FSE Assets
73 */
74 public function fse_assets_generation( $template, $type, $templates ) {
75 $block_template = resolve_block_template( $type, $templates, $template );
76 if ( ! empty( $block_template ) ) {
77 $parsed_content = parse_blocks( $block_template->content );
78 if ( is_array( $parsed_content ) && ! empty( $parsed_content ) ) {
79 foreach ( $parsed_content as $content ) {
80 if ( ( 'core/template-part' == $content[ 'blockName' ] ) || ( 'core/template' == $content[ 'blockName' ] ) ) {
81 $post_ids = isset( $content[ 'attrs' ][ 'slug' ] ) ? self::betterdocs_get_post_content_by_post_name( $content[ 'attrs' ][ 'slug' ] ) : array( );
82
83 if ( ! empty( $post_ids ) ) {
84 foreach ( $post_ids as $id ) {
85 $post_id = (int) $id[ 'ID' ];
86 self::$_fse_template_post_ids[ ] = $post_id;
87 $post = get_post( $post_id );
88 $parsed_content = parse_blocks( $post->post_content );
89 $this->write_css_from_content( $post, $post_id, $parsed_content );
90 }
91 }
92 } else {
93 $post_ids = self::betterdocs_get_post_content_by_post_name( $block_template->slug );
94 if ( ! empty( $post_ids ) ) {
95 foreach ( $post_ids as $id ) {
96 $post_id = (int) $id[ 'ID' ];
97 self::$_fse_template_post_ids[ ] = $post_id;
98 $post = get_post( $post_id );
99 $parsed_content = parse_blocks( $post->post_content );
100 $this->write_css_from_content( $post, $post_id, $parsed_content );
101 }
102 }
103 }
104 }
105 }
106 }
107
108 return $template;
109 }
110
111 /**
112 * Write CSS
113 */
114 public function write_css_from_content( $post, $post_id, $parsed_content ) {
115 $betterdocs_blocks = array( );
116 $recursive_response = CSSParser::betterdocs_block_style_recursive( $parsed_content, $betterdocs_blocks );
117 $reusable_Blocks = ! empty( $recursive_response[ 'reusableBlocks' ] ) ? $recursive_response[ 'reusableBlocks' ] : array( );
118 // remove empty reusable blocks
119 $reusable_Blocks = array_filter(
120 $reusable_Blocks,
121 function ( $v ) {
122 return ! empty( $v );
123 }
124 );
125 unset( $recursive_response[ 'reusableBlocks' ] );
126 $style = CSSParser::blocks_to_style_array( $recursive_response );
127 $reusableIds = $reusable_Blocks ? array_keys( $reusable_Blocks ) : array( );
128 if ( ! empty( $reusableIds ) ) {
129 update_option( '_betterdocs_reusable_block_ids', $reusableIds );
130 }
131 update_post_meta( $post_id, '_betterdocs_reusable_block_ids', $reusableIds );
132 $this->write_block_css( $style, $post ); //Write CSS file for this page
133
134 if ( ! empty( $reusable_Blocks ) ) {
135 foreach ( $reusable_Blocks as $blockId => $block ) {
136 $style = CSSParser::blocks_to_style_array( $block );
137 $this->write_reusable_block_css( $style, $blockId );
138 }
139 }
140 }
141
142 /**
143 * Save Widget CSS when Widget is saved
144 * @return void
145 * @since 3.5.3
146 */
147 public function after_save_widget( $id, $sidebar_id, $request, $creating ) {
148 $parsed_content = isset( $request[ 'instance' ][ 'raw' ][ 'content' ] ) ? parse_blocks( $request[ 'instance' ][ 'raw' ][ 'content' ] ) : array( );
149 if ( is_array( $parsed_content ) && ! empty( $parsed_content ) ) {
150 $betterdocs_blocks = array( );
151 $recursive_response = CSSParser::betterdocs_block_style_recursive( $parsed_content, $betterdocs_blocks );
152 unset( $recursive_response[ 'reusableBlocks' ] );
153 $style = CSSParser::blocks_to_style_array( $recursive_response );
154 //Write CSS file for Widget
155 $this->single_file_css_generator( $style, $this->style_dir, $this->prefix . '-widget.min.css' );
156 }
157 }
158
159 /**
160 * Load Dependencies
161 */
162 private function load_style_handler_dependencies() {
163 require_once plugin_dir_path( __FILE__ ) . 'includes/class-parse-css.php';
164 }
165
166 /**
167 * Enqueue frontend css for post if have one
168 * @return void
169 * @since 1.0.2
170 */
171 public function enqueue_frontend_assets() {
172 global $post;
173 $deps = apply_filters( 'betterdocs_generated_css_frontend_deps', array( ) );
174
175 // generatepress elements
176 if ( in_array( 'gp-premium/gp-premium.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
177 $gp_elements = get_posts( array( 'post_type' => 'gp_elements' ) );
178 if ( is_array( $gp_elements ) && ! empty( $gp_elements ) ) {
179 foreach ( $gp_elements as $element ) {
180 if ( file_exists( $this->style_dir . $this->prefix . '-' . $element->ID . '.min.css' ) ) {
181 wp_enqueue_style( 'betterdocs-block-style-' . $element->ID, $this->style_url . $this->prefix . '-' . $element->ID . '.min.css', $deps, substr( md5( microtime( true ) ), 0, 10 ) );
182 }
183 }
184 }
185 }
186
187 if ( ! empty( $post ) && ! empty( $post->ID ) ) {
188 //Page/Post Style Enqueue
189 if ( file_exists( $this->style_dir . $this->prefix . '-' . $post->ID . '.min.css' ) ) {
190 wp_enqueue_style( 'betterdocs-block-style-' . $post->ID, $this->style_url . $this->prefix . '-' . $post->ID . '.min.css', $deps, substr( md5( microtime( true ) ), 0, 10 ) );
191 }
192
193 // Reusable block Style Enqueues
194 $reusableIds = get_post_meta( $post->ID, '_betterdocs_reusable_block_ids', true );
195 $reusableIds = ! empty( $reusableIds ) ? $reusableIds : array( );
196 $templateReusableIds = get_option( '_betterdocs_reusable_block_ids', array( ) );
197 $reusableIds = array_unique( array_merge( $reusableIds, $templateReusableIds ) );
198 if ( ! empty( $reusableIds ) ) {
199 foreach ( $reusableIds as $reusableId ) {
200 if ( file_exists( $this->style_dir . 'reusable-blocks/betterdocs-reusable-' . $reusableId . '.min.css' ) ) {
201 wp_enqueue_style( 'betterdocs-reusable-block-style-' . $reusableId, $this->style_url . 'reusable-blocks/betterdocs-reusable-' . $reusableId . '.min.css', $deps, substr( md5( microtime( true ) ), 0, 10 ) );
202 }
203 }
204 }
205 }
206
207 //Widget Style Enqueue
208 if ( file_exists( $this->style_dir . $this->prefix . '-widget.min.css' ) ) {
209 wp_enqueue_style( 'betterdocs-widget-style', $this->style_url . $this->prefix . '-widget.min.css', $deps, substr( md5( microtime( true ) ), 0, 10 ) );
210 }
211
212 //FSE Style Enqueue — one file per FSE template/template-part post so
213 // styles do not bleed across templates (issue #53).
214 if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() && betterdocs()->helper->is_templates() && ! empty( self::$_fse_template_post_ids ) ) {
215 foreach ( array_unique( self::$_fse_template_post_ids ) as $template_post_id ) {
216 $file_name = $this->prefix . '-edit-site-' . abs( $template_post_id ) . '.min.css';
217 if ( file_exists( $this->style_dir . $file_name ) ) {
218 wp_enqueue_style(
219 'betterdocs-fullsite-style-' . abs( $template_post_id ),
220 $this->style_url . $file_name,
221 $deps,
222 substr( md5( microtime( true ) ), 0, 10 )
223 );
224 }
225 }
226 }
227
228 /**
229 * Hooks assets for enqueue in frontend
230 *
231 * @param $path string
232 * @param $url string
233 *
234 * @since 3.0.0
235 */
236 do_action( 'betterdocs_frontend_assets', $this->style_dir, $this->style_url );
237 }
238
239 /**
240 * Get post content when page is saved
241 */
242 public function on_save_post( $post_id, $post, $update ) {
243 $post_type = get_post_type( $post_id );
244
245 //If This page is draft, return
246 if ( isset( $post->post_status ) && 'auto-draft' == $post->post_status ) {
247 return;
248 }
249
250 // Autosave, do nothing
251 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
252 return;
253 }
254
255 // Return if it's a post revision
256 if ( false !== wp_is_post_revision( $post_id ) ) {
257 return;
258 }
259
260 $parsed_content = $this->get_parsed_content( $post_id, $post, $post_type );
261
262 if ( is_array( $parsed_content ) && ! empty( $parsed_content ) ) {
263 $this->write_css_from_content( $post, $post_id, $parsed_content );
264 }
265 }
266
267 private function get_parsed_content( $post_id, $post, $post_type ) {
268 if ( 'wp_template_part' === $post_type || 'wp_template' === $post_type ) {
269 $post = get_post( $post_id );
270 }
271
272 if ( ! $post ) {
273 return array( );
274 }
275
276 $parsed_content = parse_blocks( $post->post_content );
277
278 if ( empty( $parsed_content ) ) {
279 delete_post_meta( $post_id, '_betterdocs_reusable_block_ids' );
280 }
281
282 return $parsed_content;
283 }
284
285 /**
286 * Get post content when page is load in frontend
287 */
288 public function generate_post_content() {
289 $post_id = get_the_ID();
290 if ( $post_id ) {
291 $post_type = get_post_type( $post_id );
292 $post = get_post( $post_id );
293 //If This page is draft, return
294 if ( isset( $post->post_status ) && 'auto-draft' == $post->post_status ) {
295 return;
296 }
297
298 // Return if it's a post revision
299 if ( false !== wp_is_post_revision( $post_id ) ) {
300 return null;
301 }
302
303 $parsed_content = $this->get_parsed_content( $post_id, $post, $post_type );
304
305 if ( is_array( $parsed_content ) && ! empty( $parsed_content ) ) {
306 $this->write_css_from_content( $post, $post_id, $parsed_content );
307 }
308 }
309 }
310
311 /**
312 * Ajax callback to write css in upload directory
313 * @retun void
314 * @since 1.0.2
315 */
316 private function write_block_css( $block_styles, $post ) {
317 // Write CSS for FSE templates / template parts — one file per post so
318 // styles from different templates do not overwrite each other in a
319 // shared file (issue #53).
320 if ( isset( $post->post_type ) && ( 'wp_template_part' === $post->post_type || 'wp_template' === $post->post_type ) ) {
321 if ( empty( $block_styles ) ) {
322 return;
323 }
324 if ( ! empty( $css = CSSParser::build_css( $block_styles ) ) ) {
325 if ( ! file_exists( $this->style_dir ) ) {
326 mkdir( $this->style_dir );
327 }
328 file_put_contents( $this->style_dir . $this->prefix . '-edit-site-' . abs( $post->ID ) . '.min.css', $css );
329 }
330 } // Write CSS for Page/Posts
331 elseif ( ! empty( $css = CSSParser::build_css( $block_styles ) ) ) {
332 if ( ! file_exists( $this->style_dir ) ) {
333 mkdir( $this->style_dir );
334 }
335 file_put_contents( $this->style_dir . $this->prefix . '-' . abs( $post->ID ) . '.min.css', $css );
336 }
337 }
338
339 /**
340 * Write css for Reusable block
341 * @retun void
342 * @since 3.4.0
343 */
344 private function write_reusable_block_css( $block_styles, $id ) {
345 if ( isset( $block_styles ) && is_array( $block_styles ) ) {
346 if ( ! empty( $css = CSSParser::build_css( $block_styles ) ) ) {
347 $upload_dir = $this->style_dir . 'reusable-blocks/';
348 if ( ! file_exists( $upload_dir ) ) {
349 mkdir( $upload_dir, 0777, true );
350 }
351 file_put_contents( $upload_dir . '/' . 'betterdocs-reusable-' . abs( $id ) . '.min.css', $css );
352 }
353 }
354 }
355
356 /**
357 * Single file css generator
358 * @retun void
359 * @since 3.5.3
360 */
361 private function single_file_css_generator( $block_styles, $upload_dir, $filename ) {
362 $editSiteCssPath = $upload_dir . $filename;
363 if ( file_exists( $editSiteCssPath ) ) {
364 $existingCss = file_get_contents( $editSiteCssPath );
365 $pattern = '~\/\*(.*?)\*\/~';
366 preg_match_all( $pattern, $existingCss, $result, PREG_PATTERN_ORDER );
367 $allComments = $result[ 0 ];
368 $seperatedIds = array( );
369 foreach ( $allComments as $comment ) {
370 $id = preg_replace( '/[^A-Za-z0-9\-]|Ends|Starts/', '', $comment );
371
372 if ( strpos( $comment, 'Starts' ) ) {
373 $seperatedIds[ $id ][ 'start' ] = $comment;
374 } elseif ( strpos( $comment, 'Ends' ) ) {
375 $seperatedIds[ $id ][ 'end' ] = $comment;
376 }
377 }
378
379 $seperateStyles = array( );
380 foreach ( $seperatedIds as $key => $ids ) {
381 $seperateStyles[ ][ $key ] = isset( $block_styles[ $key ] ) ? $block_styles[ $key ] : array( );
382 }
383
384 self::$_block_styles = array_merge( self::$_block_styles, $block_styles );
385
386 if ( ! empty( $css = CSSParser::build_css( self::$_block_styles ) ) ) {
387 if ( ! file_exists( $upload_dir ) ) {
388 mkdir( $upload_dir );
389 }
390
391 file_put_contents( $editSiteCssPath, $css );
392 }
393 } elseif ( ! empty( $css = CSSParser::build_css( $block_styles ) ) ) {
394 if ( ! file_exists( $this->style_dir ) ) {
395 mkdir( $this->style_dir );
396 }
397
398 file_put_contents( $editSiteCssPath, $css );
399 }
400 }
401
402 /**
403 * Get post id by post_name for template
404 */
405 public static function betterdocs_get_post_content_by_post_name( $post_name ) {
406 global $wpdb;
407 $sql = $wpdb->prepare( "SELECT ID FROM {$wpdb->prefix}posts WHERE post_name = %s", $post_name );
408
409 return $wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
410 }
411 }
412