PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.0.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.0.0
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.0.0, at includes/Modules/StyleHandler.php

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