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

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