PATH:
home
/
thebhoeo
/
.trash
/
otter-blocks
/
inc
/
plugins
<?php /** * Atomic Wind Blocks module. * * @package ThemeIsle\GutenbergBlocks\Plugins */ namespace ThemeIsle\GutenbergBlocks\Plugins; /** * Atomic Wind Blocks * * Registers the 5 primitive Atomic Wind blocks and all supporting subsystems * (Tailwind CSS pipeline, animations, states, query, class editor). */ class Atomic_Wind_Blocks { /** * Whether we are currently inside a query loop render. * * @var bool */ private static $in_query = false; /** * Initialize the module. * * @return void */ public function instance() { $this->run(); } /** * Run the module if the option is enabled. * * @return void */ public function run() { if ( ! get_option( 'themeisle_blocks_settings_atomic_wind_blocks', false ) ) { return; } $this->register_blocks(); add_action( 'enqueue_block_assets', array( $this, 'enqueue_tailwind_generator' ) ); add_action( 'enqueue_block_assets', array( $this, 'enqueue_base_css' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_icons_data' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_editor_assets' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'output_cached_css' ) ); add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'register_frontend_animations' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'register_frontend_states' ) ); add_filter( 'render_block', array( $this, 'render_query_loop' ), 5, 2 ); add_filter( 'render_block', array( $this, 'render_post_fields' ), 20, 2 ); add_filter( 'render_block', array( $this, 'render_animation_attrs' ), 10, 2 ); add_filter( 'render_block', array( $this, 'render_state_attrs' ), 10, 2 ); add_action( 'save_post', array( $this, 'clear_cached_css' ) ); add_filter( 'block_categories_all', array( $this, 'register_category' ) ); } /** * Check if a post contains any atomic-wind/* block. * * Uses a direct strpos on post_content (already in memory) — no extra DB query. * has_block() requires a full block name and normalizes non-namespaced names to * core/*, so it cannot be used for namespace-prefix matching. * * @param \WP_Post $post Post object. * @return bool */ private function post_has_atomic_wind_blocks( \WP_Post $post ) { return has_blocks( $post ) && false !== strpos( $post->post_content, '<!-- wp:atomic-wind/' ); } /** * Get the path to the plugin root. * * @return string */ private function base_path() { return OTTER_BLOCKS_PATH; } /** * Get the build directory path. * * @return string */ private function build_path() { return $this->base_path() . '/build/atomic-wind'; } /** * Get a plugins_url relative to the otter-blocks root. * * @param string $path Relative path. * @return string */ private function plugin_url( $path ) { return plugins_url( $path, $this->base_path() . '/otter-blocks.php' ); } /** * Register all Atomic Wind block types from the build directory. * * @return void */ private function register_blocks() { $build_dir = $this->build_path() . '/blocks'; if ( ! is_dir( $build_dir ) ) { return; } foreach ( scandir( $build_dir ) as $block ) { if ( '.' === $block || '..' === $block ) { continue; } $block_path = $build_dir . '/' . $block; if ( is_dir( $block_path ) && file_exists( $block_path . '/block.json' ) ) { register_block_type( $block_path ); } } } /** * Enqueue the Tailwind CSS generator in the editor. * * @return void */ public function enqueue_tailwind_generator() { if ( ! is_admin() ) { return; } $asset_file = $this->build_path() . '/tailwind-generator-editor.asset.php'; if ( ! file_exists( $asset_file ) ) { return; } $asset = include $asset_file; wp_enqueue_script( 'atomic-wind-tailwind-generator', $this->plugin_url( 'build/atomic-wind/tailwind-generator-editor.js' ), $asset['dependencies'], $asset['version'], true ); } /** * Enqueue base CSS that zeroes out margins for all atomic-wind blocks. * * @return void */ public function enqueue_base_css() { global $post; if ( ! $post || ! $this->post_has_atomic_wind_blocks( $post ) ) { return; } wp_register_style( 'atomic-wind-base', false, [], OTTER_BLOCKS_VERSION ); wp_enqueue_style( 'atomic-wind-base' ); $css = '[class*="wp-block-atomic-wind-"]{margin:0;max-width:unset;}[class*="wp-block-atomic-wind-"] p{margin:0;}'; if ( is_admin() ) { $css .= '.editor-styles-wrapper .wp-block[class*="wp-block-atomic-wind-"]{margin:0;max-width:unset;}.editor-styles-wrapper [class*="wp-block-atomic-wind-"] p{margin:0;}'; } wp_add_inline_style( 'atomic-wind-base', $css ); } /** * Inject window.atomicWindIcons data for the icon block editor UI. * * @return void */ public function enqueue_icons_data() { $json_path = $this->base_path() . '/assets/atomic-wind/icons.json'; $icons = array(); $icons_map = new \stdClass(); if ( is_file( $json_path ) ) { if ( function_exists( 'wpcom_vip_file_get_contents' ) ) { $content = wpcom_vip_file_get_contents( $json_path ); } else { $content = file_get_contents( $json_path ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown } if ( $content ) { $map = json_decode( $content, true ); if ( is_array( $map ) ) { $icons = array_keys( $map ); $icons_map = $map; } } } wp_add_inline_script( 'wp-blocks', 'window.atomicWindIcons = ' . wp_json_encode( array( 'icons' => $icons, 'iconsMap' => $icons_map, ) ) . ';' . 'window.atomicWindEditor = ' . wp_json_encode( array( 'restUrl' => rest_url( 'otter/v1/atomic-wind' ), 'nonce' => wp_create_nonce( 'wp_rest' ), ) ) . ';', 'before' ); } /** * Enqueue the unified editor script and styles (animations, states, query, class editor). * * @return void */ public function enqueue_editor_assets() { $asset_file = $this->build_path() . '/editor.asset.php'; if ( ! file_exists( $asset_file ) ) { return; } $asset = include $asset_file; wp_enqueue_script( 'atomic-wind-editor', $this->plugin_url( 'build/atomic-wind/editor.js' ), array_merge( $asset['dependencies'], array( 'wp-blocks', 'wp-hooks' ) ), $asset['version'], true ); wp_enqueue_style( 'atomic-wind-editor', $this->plugin_url( 'build/atomic-wind/style-editor.css' ), array(), $asset['version'] ); } /** * Output cached Tailwind CSS or enqueue the generator + style-builder. * * @return void */ public function output_cached_css() { global $post; if ( ! $post ) { return; } if ( ! $this->post_has_atomic_wind_blocks( $post ) ) { return; } $cached_css = get_post_meta( $post->ID, '_atomic_wind_css', true ); if ( $cached_css ) { wp_register_style( 'atomic-wind-tailwind', false, [], OTTER_BLOCKS_VERSION ); wp_enqueue_style( 'atomic-wind-tailwind' ); wp_add_inline_style( 'atomic-wind-tailwind', $cached_css ); return; } $generator_asset = $this->build_path() . '/tailwind-generator-frontend.asset.php'; if ( ! file_exists( $generator_asset ) ) { return; } $gen = include $generator_asset; wp_enqueue_script( 'atomic-wind-tailwind-generator', $this->plugin_url( 'build/atomic-wind/tailwind-generator-frontend.js' ), $gen['dependencies'], $gen['version'], true ); if ( is_user_logged_in() && current_user_can( 'edit_post', $post->ID ) ) { $builder_asset = $this->build_path() . '/style-builder.asset.php'; if ( file_exists( $builder_asset ) ) { $sb = include $builder_asset; wp_enqueue_script( 'atomic-wind-style-builder', $this->plugin_url( 'build/atomic-wind/style-builder.js' ), $sb['dependencies'], $sb['version'], true ); wp_localize_script( 'atomic-wind-style-builder', 'atomicWindStyleBuilder', array( 'postId' => $post->ID, 'restUrl' => rest_url( 'otter/v1/atomic-wind' ), 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); } } } /** * Register REST API route for saving generated CSS. * * @return void */ public function register_rest_routes() { register_rest_route( 'otter/v1', '/atomic-wind/style', array( 'methods' => 'POST', 'callback' => array( $this, 'rest_save_style' ), 'permission_callback' => array( $this, 'rest_save_style_permissions' ), 'args' => array( 'css' => array( 'type' => 'string', 'required' => true, 'sanitize_callback' => array( $this, 'sanitize_css' ), ), 'postId' => array( 'type' => 'integer', 'required' => true, ), ), ) ); } /** * REST callback: save generated Tailwind CSS to post meta. * * @param \WP_REST_Request $request Request object. * @phpstan-param \WP_REST_Request<array<string, mixed>> $request * @return \WP_REST_Response */ public function rest_save_style( \WP_REST_Request $request ) { $post_id = absint( $request->get_param( 'postId' ) ); $css = $request->get_param( 'css' ); $success = update_post_meta( $post_id, '_atomic_wind_css', wp_slash( $css ) ); return new \WP_REST_Response( array( 'success' => $success ), 200 ); } /** * REST permission callback for the style endpoint. * * @param \WP_REST_Request $request Request object. * @phpstan-param \WP_REST_Request<array<string, mixed>> $request * @return bool */ public function rest_save_style_permissions( \WP_REST_Request $request ) { $post_id = absint( $request->get_param( 'postId' ) ); return current_user_can( 'edit_post', $post_id ); } /** * Clear cached Tailwind CSS when a post is saved. * * Forces the frontend to regenerate CSS on the next page load. * * @param int $post_id Post ID. * @return void */ public function clear_cached_css( $post_id ) { delete_post_meta( $post_id, '_atomic_wind_css' ); } /** * Sanitize CSS for storage without stripping @property syntax descriptors. * * Strpping with wp_strip_all_tags destroys valid CSS like syntax: "<color>" because it * looks like an HTML tag. This method only removes patterns that could * break out of a <style> context. * * @param string $css Raw CSS string. * @return string Sanitized CSS. */ public function sanitize_css( $css ) { $css = wp_check_invalid_utf8( $css ); $css = preg_replace( '/<\/?style\b[^>]*>/i', '', $css ); $css = preg_replace( '/<\/?script\b[^>]*>/i', '', $css ); $css = preg_replace( '/<!--.*?-->/s', '', $css ); return $css; } /** * Register animations frontend CSS and JS for conditional enqueue. * * @return void */ public function register_frontend_animations() { $asset_file = $this->build_path() . '/animations-frontend.asset.php'; if ( ! file_exists( $asset_file ) ) { return; } $asset = include $asset_file; wp_register_style( 'atomic-wind-animations', $this->plugin_url( 'build/atomic-wind/style-animations-frontend.css' ), array(), $asset['version'] ); wp_register_script( 'atomic-wind-animations-frontend', $this->plugin_url( 'build/atomic-wind/animations-frontend.js' ), $asset['dependencies'], $asset['version'], true ); } /** * Register states frontend JS for conditional enqueue. * * @return void */ public function register_frontend_states() { $asset_file = $this->build_path() . '/states-frontend.asset.php'; if ( ! file_exists( $asset_file ) ) { return; } $asset = include $asset_file; wp_register_script( 'atomic-wind-states-frontend', $this->plugin_url( 'build/atomic-wind/states-frontend.js' ), $asset['dependencies'], $asset['version'], true ); } /** * Render query loop for box blocks with queryPostType. * * @param string $block_content Block content. * @param array<string, mixed> $block Block data. * @return string */ public function render_query_loop( $block_content, $block ) { $block_name = isset( $block['blockName'] ) ? $block['blockName'] : ''; if ( 'atomic-wind/box' !== $block_name ) { return $block_content; } $post_type = isset( $block['attrs']['queryPostType'] ) ? $block['attrs']['queryPostType'] : ''; if ( ! $post_type ) { return $block_content; } global $post; $saved_post = $post; $args = array( 'post_type' => sanitize_key( $post_type ), 'posts_per_page' => isset( $block['attrs']['queryCount'] ) ? min( absint( $block['attrs']['queryCount'] ), 100 ) : 3, 'orderby' => isset( $block['attrs']['queryOrderBy'] ) ? sanitize_key( $block['attrs']['queryOrderBy'] ) : sanitize_key( 'date' ), 'order' => isset( $block['attrs']['queryOrder'] ) && strtoupper( $block['attrs']['queryOrder'] ) === 'ASC' ? 'ASC' : 'DESC', 'no_found_rows' => true, ); $taxonomy_filter = isset( $block['attrs']['queryTaxonomy'] ) ? sanitize_text_field( $block['attrs']['queryTaxonomy'] ) : ''; if ( $taxonomy_filter && false !== strpos( $taxonomy_filter, ':' ) ) { $parts = explode( ':', $taxonomy_filter, 2 ); $tax = sanitize_key( $parts[0] ); $term = sanitize_key( $parts[1] ); if ( $tax && $term ) { $args['tax_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query array( 'taxonomy' => $tax, 'field' => 'slug', 'terms' => array( $term ), ), ); } } if ( ! empty( $block['attrs']['queryExcludeCurrent'] ) && $saved_post ) { $args['post__not_in'] = array( $saved_post->ID ); // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in } $sticky = isset( $block['attrs']['querySticky'] ) ? sanitize_key( $block['attrs']['querySticky'] ) : ''; if ( 'exclude' === $sticky ) { $args['ignore_sticky_posts'] = true; } elseif ( 'only' === $sticky ) { $sticky_ids = get_option( 'sticky_posts' ); if ( ! empty( $sticky_ids ) ) { $args['post__in'] = $sticky_ids; $args['ignore_sticky_posts'] = true; } else { return ''; } } $query = new \WP_Query( $args ); if ( ! $query->have_posts() ) { wp_reset_postdata(); $post = $saved_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited return ''; } preg_match( '/^\s*(<[^>]+>)/s', $block_content, $open_match ); preg_match( '/(<\/[a-zA-Z0-9]+>)\s*$/s', $block_content, $close_match ); $opening_tag = isset( $open_match[1] ) ? $open_match[1] : ''; $closing_tag = isset( $close_match[1] ) ? $close_match[1] : ''; $loop_output = ''; self::$in_query = true; while ( $query->have_posts() ) { $query->the_post(); foreach ( $block['innerBlocks'] as $inner_block ) { $loop_output .= render_block( $inner_block ); } } self::$in_query = false; wp_reset_postdata(); $post = $saved_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited return $opening_tag . $loop_output . $closing_tag; } /** * Replace block content with post field data inside query loops. * * @param string $block_content Block content. * @param array<string, mixed> $block Block data. * @return string */ public function render_post_fields( $block_content, $block ) { $block_name = isset( $block['blockName'] ) ? $block['blockName'] : ''; if ( 0 !== strpos( $block_name, 'atomic-wind/' ) ) { return $block_content; } $post_field = isset( $block['attrs']['postField'] ) ? $block['attrs']['postField'] : ''; if ( ! $post_field || ! self::$in_query ) { return $block_content; } $block_name = $block['blockName']; if ( 'atomic-wind/text' === $block_name ) { $value = ''; switch ( $post_field ) { case 'title': $value = get_the_title(); break; case 'excerpt': $length = isset( $block['attrs']['excerptLength'] ) ? absint( $block['attrs']['excerptLength'] ) : 25; $raw = get_the_excerpt(); $value = wp_trim_words( wp_strip_all_tags( $raw ), $length, '…' ); break; case 'date': $value = get_the_date(); break; case 'author': $value = get_the_author(); break; case 'categories': $cats = get_the_category(); if ( $cats ) { $value = implode( ', ', wp_list_pluck( $cats, 'name' ) ); } break; case 'tags': $tags = get_the_tags(); if ( $tags ) { $value = implode( ', ', wp_list_pluck( $tags, 'name' ) ); } break; case 'modified_date': $value = get_the_modified_date(); break; case 'comment_count': $value = (string) get_comments_number(); break; case 'reading_time': $content = get_the_content(); $word_count = str_word_count( wp_strip_all_tags( $content ) ); $minutes = max( 1, (int) ceil( $word_count / 200 ) ); /* translators: %d: number of minutes */ $value = sprintf( _n( '%d min read', '%d min read', $minutes, 'otter-blocks' ), $minutes ); break; case 'custom_field': $meta_key = isset( $block['attrs']['customFieldKey'] ) ? sanitize_text_field( $block['attrs']['customFieldKey'] ) : ''; if ( $meta_key ) { $value = ''; if ( function_exists( 'get_field' ) ) { $acf = get_field( $meta_key, get_the_ID() ); if ( is_string( $acf ) ) { $value = $acf; } } if ( ! $value ) { $raw = get_post_meta( get_the_ID(), $meta_key, true ); if ( is_string( $raw ) ) { $value = $raw; } } } break; } if ( $value ) { $escaped = esc_html( $value ); $block_content = preg_replace_callback( '/^\s*(<[^>]+>).*(<\/[a-zA-Z0-9]+>)\s*$/s', function ( $m ) use ( $escaped ) { return $m[1] . $escaped . $m[2]; }, $block_content ); } } elseif ( 'atomic-wind/link' === $block_name ) { $url = ''; switch ( $post_field ) { case 'permalink': $url = get_the_permalink(); break; case 'author_posts_url': $url = get_author_posts_url( (int) get_the_author_meta( 'ID' ) ); break; case 'category_link': $cats = get_the_category(); if ( $cats && ! empty( $cats[0] ) ) { $url = get_category_link( $cats[0]->term_id ); } break; case 'tag_link': $tags = get_the_tags(); if ( $tags && ! empty( $tags[0] ) ) { $url = get_tag_link( $tags[0]->term_id ); } break; case 'date_archive': $url = get_month_link( get_the_date( 'Y' ), get_the_date( 'n' ) ); break; case 'author_archive': $url = get_author_posts_url( (int) get_the_author_meta( 'ID' ) ); break; } if ( $url ) { $href = 'href="' . esc_url( $url ) . '"'; if ( false !== strpos( $block_content, 'href="' ) ) { $block_content = preg_replace( '/href="[^"]*"/', $href, $block_content ); } else { $block_content = preg_replace( '/^(\s*<a\b)/', '$1 ' . $href, $block_content ); } } } elseif ( 'atomic-wind/image' === $block_name ) { if ( 'featured_image' === $post_field ) { $thumb_id = get_post_thumbnail_id(); if ( ! $thumb_id ) { return ''; } $src = wp_get_attachment_image_url( $thumb_id, 'large' ); $alt = get_post_meta( $thumb_id, '_wp_attachment_image_alt', true ); if ( ! $alt ) { $alt = get_the_title(); } $block_content = preg_replace( '/src="[^"]*"/', 'src="' . esc_url( $src ) . '"', $block_content ); $block_content = preg_replace( '/alt="[^"]*"/', 'alt="' . esc_attr( $alt ) . '"', $block_content ); } elseif ( 'author_avatar' === $post_field ) { $avatar_url = get_avatar_url( get_the_author_meta( 'ID' ), array( 'size' => 256 ) ); if ( $avatar_url ) { $alt = get_the_author(); $block_content = preg_replace( '/src="[^"]*"/', 'src="' . esc_url( $avatar_url ) . '"', $block_content ); $block_content = preg_replace( '/alt="[^"]*"/', 'alt="' . esc_attr( $alt ) . '"', $block_content ); } } } return $block_content; } /** * Inject data-animation attributes into server-rendered blocks. * * @param string $block_content Block content. * @param array<string, mixed> $block Block data. * @return string */ public function render_animation_attrs( $block_content, $block ) { $block_name = isset( $block['blockName'] ) ? $block['blockName'] : ''; if ( 0 !== strpos( $block_name, 'atomic-wind/' ) ) { return $block_content; } $animation = isset( $block['attrs']['animation'] ) ? $block['attrs']['animation'] : ''; if ( ! $animation ) { return $block_content; } wp_enqueue_style( 'atomic-wind-animations' ); wp_enqueue_script( 'atomic-wind-animations-frontend' ); if ( false !== strpos( $block_content, 'data-animation' ) ) { return $block_content; } $attrs = ' data-animation="' . esc_attr( $animation ) . '"'; $delay = isset( $block['attrs']['animationDelay'] ) ? (string) $block['attrs']['animationDelay'] : ''; if ( '' !== $delay && '0' !== $delay ) { $attrs .= ' data-animation-delay="' . esc_attr( $delay ) . '"'; } return preg_replace( '/^(<[a-zA-Z][a-zA-Z0-9]*)\b/', '$1' . $attrs, $block_content, 1 ); } /** * Inject data-show-if / data-hide-if attributes into server-rendered blocks. * * @param string $block_content Block content. * @param array<string, mixed> $block Block data. * @return string */ public function render_state_attrs( $block_content, $block ) { $block_name = isset( $block['blockName'] ) ? $block['blockName'] : ''; if ( 0 !== strpos( $block_name, 'atomic-wind/' ) ) { return $block_content; } $has_state = false; $show_if = isset( $block['attrs']['showIf'] ) ? $block['attrs']['showIf'] : ''; $hide_if = isset( $block['attrs']['hideIf'] ) ? $block['attrs']['hideIf'] : ''; $trigger = isset( $block['attrs']['stateTrigger'] ) ? $block['attrs']['stateTrigger'] : ''; $state_action = isset( $block['attrs']['stateAction'] ) ? $block['attrs']['stateAction'] : 'toggle'; $state_value = isset( $block['attrs']['stateValue'] ) ? $block['attrs']['stateValue'] : ''; $state_default = ! empty( $block['attrs']['stateDefault'] ); if ( $show_if || $hide_if || $trigger ) { $has_state = true; } if ( ! $has_state ) { return $block_content; } wp_enqueue_script( 'atomic-wind-states-frontend' ); $attrs = ''; if ( $show_if ) { $attrs .= ' data-show-if="' . esc_attr( $show_if ) . '"'; } if ( $hide_if ) { $attrs .= ' data-hide-if="' . esc_attr( $hide_if ) . '"'; } if ( $trigger ) { $attrs .= ' data-state-trigger="' . esc_attr( $trigger ) . '"'; $attrs .= ' data-state-action="' . esc_attr( $state_action ) . '"'; if ( 'set' === $state_action && $state_value ) { $attrs .= ' data-state-value="' . esc_attr( $state_value ) . '"'; } if ( $state_default ) { $attrs .= ' data-state-default'; } } if ( ! $attrs || false !== strpos( $block_content, 'data-show-if' ) || false !== strpos( $block_content, 'data-hide-if' ) || false !== strpos( $block_content, 'data-state-trigger' ) ) { return $block_content; } return preg_replace( '/^(\s*<[a-zA-Z][a-zA-Z0-9]*)\b/', '$1' . $attrs, $block_content, 1 ); } /** * Prepend the atomic-wind block category. * * @param array<int, array<string, string>> $categories Existing categories. * @return array<int, array<string, string>> */ public function register_category( $categories ) { array_unshift( $categories, array( 'slug' => 'atomic-wind', 'title' => __( 'Atomic Wind', 'otter-blocks' ), ) ); return $categories; } }
[-] class-options-settings.php
[edit]
[-] class-atomic-wind-blocks.php
[edit]
[-] class-block-conditions.php
[edit]
[-] class-fse-onboarding.php
[edit]
[-] class-dashboard.php
[edit]
[-] class-stripe-api.php
[edit]
[+]
..
[-] class-limited-offers.php
[edit]
[-] class-dynamic-content.php
[edit]
[-] class-template-cloud.php
[edit]