Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/qi-blocks/class-qi-blocks.php
+++ b/qi-blocks/class-qi-blocks.php
@@ -5,8 +5,8 @@
Author: Qode Interactive
Author URI: https://qodeinteractive.com/
Plugin URI: https://qodeinteractive.com/qi-blocks-for-gutenberg/
-Version: 1.4.9
-Requires at least: 5.8
+Version: 1.5
+Requires at least: 6.3
Requires PHP: 7.4
Text Domain: qi-blocks
*/
@@ -49,6 +49,9 @@
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'localize_js_scripts' ) );
+ // Loads core block assets only when they are rendered on the page - WordPress 5.8.
+ add_filter( 'should_load_separate_core_block_assets', '__return_true' );
+
// Register plugin's editor assets.
add_action( 'init', array( $this, 'register_editor_assets' ) );
@@ -175,10 +178,24 @@
}
public function enqueue_assets() {
+ if ( ! function_exists( 'qi_blocks_should_load_frontend_assets' ) || ! qi_blocks_should_load_frontend_assets() ) {
+ return;
+ }
- // Enqueue plugin's 3rd party scripts.
+ $this->register_3rd_party_assets();
$this->enqueue_3rd_party_assets();
+ if ( function_exists( 'qi_blocks_page_needs_swiper' ) && qi_blocks_page_needs_swiper() ) {
+ wp_enqueue_style( 'swiper' );
+ wp_enqueue_script( 'swiper' );
+
+ $wp_scripts = wp_scripts();
+
+ if ( isset( $wp_scripts->registered['qi-blocks-main'] ) && ! in_array( 'swiper', $wp_scripts->registered['qi-blocks-main']->deps, true ) ) {
+ $wp_scripts->registered['qi-blocks-main']->deps[] = 'swiper';
+ }
+ }
+
// Enqueue CSS grid styles.
wp_enqueue_style( 'qi-blocks-grid' );
@@ -190,9 +207,7 @@
}
public function register_editor_assets() {
-
- // Enqueue plugin's 3rd party scripts.
- $this->enqueue_3rd_party_assets();
+ $this->register_3rd_party_assets();
// Register CSS grid styles.
wp_register_style( 'qi-blocks-grid-editor', QI_BLOCKS_ASSETS_URL_PATH . '/dist/grid-editor.css', array( 'qi-blocks-main' ), QI_BLOCKS_VERSION );
@@ -203,6 +218,7 @@
}
public function enqueue_editor_assets() {
+ $this->enqueue_3rd_party_assets();
// Enqueue CSS grid styles.
wp_enqueue_style( 'qi-blocks-grid-editor' );
@@ -237,17 +253,22 @@
}
}
- public function enqueue_3rd_party_assets() {
-
+ public function register_3rd_party_assets() {
// Hook to include additional 3rd party scripts.
do_action( 'qi_blocks_action_additional_3rd_party_scripts' );
- // Register and enqueue animate styles.
- wp_register_style( 'animate', QI_BLOCKS_ASSETS_URL_PATH . '/css/plugins/animate/animate.min.css', array(), '4.1.1' );
- wp_enqueue_style( 'animate' );
+ if ( ! wp_style_is( 'animate', 'registered' ) ) {
+ wp_register_style( 'animate', QI_BLOCKS_ASSETS_URL_PATH . '/css/plugins/animate/animate.min.css', array(), '4.1.1' );
+ }
+
+ if ( ! wp_script_is( 'fslightbox', 'registered' ) ) {
+ wp_register_script( 'fslightbox', QI_BLOCKS_ASSETS_URL_PATH . '/js/plugins/fslightbox/fslightbox.min.js', array( 'jquery' ), '3.4.1', true );
+ }
+ }
- // Register lightbox scripts.
- wp_register_script( 'fslightbox', QI_BLOCKS_ASSETS_URL_PATH . '/js/plugins/fslightbox/fslightbox.min.js', array( 'jquery' ), '3.4.1', true );
+ public function enqueue_3rd_party_assets() {
+ $this->register_3rd_party_assets();
+ wp_enqueue_style( 'animate' );
}
public function set_block_style_dependency( $style_dependency ) {
@@ -263,6 +284,10 @@
}
public function localize_js_scripts() {
+ if ( ! wp_script_is( 'qi-blocks-main', 'enqueued' ) ) {
+ return;
+ }
+
$global = apply_filters(
'qi_blocks_filter_localize_main_js',
array(
--- a/qi-blocks/constants.php
+++ b/qi-blocks/constants.php
@@ -5,7 +5,7 @@
exit;
}
-define( 'QI_BLOCKS_VERSION', '1.4.9' );
+define( 'QI_BLOCKS_VERSION', '1.5' );
define( 'QI_BLOCKS_ABS_PATH', __DIR__ );
define( 'QI_BLOCKS_REL_PATH', dirname( plugin_basename( __FILE__ ) ) );
define( 'QI_BLOCKS_URL_PATH', plugin_dir_url( __FILE__ ) );
--- a/qi-blocks/inc/admin/global-styles/class-qi-blocks-framework-global-styles.php
+++ b/qi-blocks/inc/admin/global-styles/class-qi-blocks-framework-global-styles.php
@@ -104,6 +104,10 @@
$options = get_option( 'qi_blocks_global_styles' );
$page_id = isset( $_GET['page_id'] ) && ! empty( $_GET['page_id'] ) ? sanitize_text_field( $_GET['page_id'] ) : '';
+ if ( ! $this->user_can_manage_global_styles_target( $page_id ) ) {
+ qi_blocks_get_ajax_status( 'error', esc_html__( 'You are not allowed to access these options.', 'qi-blocks' ), array() );
+ }
+
if ( isset( $options ) ) {
if ( isset( $options['widgets'] ) && 'widget' === $page_id ) {
@@ -133,6 +137,10 @@
$options = $this->sanitize_global_options( $response_data->options );
$page_id = isset( $response_data->page_id ) && ! empty( $response_data->page_id ) ? esc_attr( $response_data->page_id ) : '';
+ if ( ! $this->user_can_manage_global_styles_target( $page_id ) ) {
+ qi_blocks_get_ajax_status( 'error', esc_html__( 'You are not allowed to update these options.', 'qi-blocks' ) );
+ }
+
// Sanitize and validate CSS options.
if ( isset( $global_options['widgets'] ) && 'widget' === $page_id ) {
$global_options['widgets'] = $options;
@@ -154,6 +162,35 @@
}
/**
+ * Verify object-level authorization for a global styles target.
+ *
+ * @param string $page_id Numeric post ID, or one of: widget, template.
+ *
+ * @return bool
+ */
+ private function user_can_manage_global_styles_target( $page_id ) {
+ if ( 'widget' === $page_id || 'template' === $page_id ) {
+ return current_user_can( 'edit_theme_options' );
+ }
+
+ if ( '' === $page_id ) {
+ return current_user_can( 'edit_theme_options' );
+ }
+
+ if ( is_numeric( $page_id ) ) {
+ $post_id = (int) $page_id;
+
+ if ( $post_id <= 0 ) {
+ return false;
+ }
+
+ return current_user_can( 'edit_post', $post_id );
+ }
+
+ return false;
+ }
+
+ /**
* Sanitize qi-blocks $options array received via AJAX.
*
* @param mixed $raw_options The raw options (decoded JSON -> array/object).
@@ -342,10 +379,30 @@
return $sanitized_options;
}
+ public function has_configured_global_styles( $global_styles ) {
+ if ( empty( $global_styles ) || ! is_array( $global_styles ) ) {
+ return false;
+ }
+
+ foreach ( $global_styles as $section ) {
+ if ( empty( $section ) || ! is_array( $section ) ) {
+ continue;
+ }
+
+ foreach ( $section as $item ) {
+ if ( ! empty( $item ) ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
public function add_page_inline_style() {
$global_styles = get_option( 'qi_blocks_global_styles' );
- if ( ! empty( $global_styles ) ) {
+ if ( $this->has_configured_global_styles( $global_styles ) ) {
$page_id = apply_filters( 'qi_blocks_filter_page_inline_style_page_id', get_queried_object_id() );
$styles = array();
$fonts = array(
--- a/qi-blocks/inc/blocks/class-qi-blocks-blocks.php
+++ b/qi-blocks/inc/blocks/class-qi-blocks-blocks.php
@@ -5,6 +5,20 @@
if ( ! class_exists( 'Qi_Blocks_Blocks' ) ) {
class Qi_Blocks_Blocks {
+ /**
+ * Aggregated 3rd party scripts from all block instances.
+ *
+ * @var array
+ */
+ private static $global_block_3rd_party_scripts = array();
+
+ /**
+ * Whether frontend/editor 3rd party script hooks are registered.
+ *
+ * @var bool
+ */
+ private static $block_3rd_party_hooks_registered = false;
+
private $blocks_namespace;
private $block_type;
private $block_name;
@@ -54,13 +68,6 @@
// Register block.
add_action( 'init', array( $this, 'register_block' ) );
-
- // Loads core block assets only when they are rendered on the page - WordPress 5.8.
- add_filter( 'should_load_separate_core_block_assets', '__return_true' );
-
- // Enqueue block 3rd party plugin's assets.
- add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_3rd_party_scripts' ) );
- add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_3rd_party_editor_scripts' ) );
}
public function get_blocks_namespace() {
@@ -165,6 +172,70 @@
public function set_block_3rd_party_scripts( $block_3rd_party_scripts ) {
$this->block_3rd_party_scripts = $block_3rd_party_scripts;
+
+ if ( ! empty( $block_3rd_party_scripts ) && is_array( $block_3rd_party_scripts ) ) {
+ foreach ( $block_3rd_party_scripts as $script_key => $script_value ) {
+ if ( isset( self::$global_block_3rd_party_scripts[ $script_key ] ) ) {
+ if ( ! empty( $script_value['block_name'] ) ) {
+ if ( empty( self::$global_block_3rd_party_scripts[ $script_key ]['block_names'] ) ) {
+ self::$global_block_3rd_party_scripts[ $script_key ]['block_names'] = array();
+ }
+
+ if ( ! empty( self::$global_block_3rd_party_scripts[ $script_key ]['block_name'] ) ) {
+ self::$global_block_3rd_party_scripts[ $script_key ]['block_names'][] = self::$global_block_3rd_party_scripts[ $script_key ]['block_name'];
+ unset( self::$global_block_3rd_party_scripts[ $script_key ]['block_name'] );
+ }
+
+ self::$global_block_3rd_party_scripts[ $script_key ]['block_names'][] = $script_value['block_name'];
+ }
+
+ continue;
+ }
+
+ if ( ! empty( $script_value['block_name'] ) ) {
+ $script_value['block_names'] = array( $script_value['block_name'] );
+ $script_value['block_name'] = $script_value['block_names'][0];
+ }
+
+ self::$global_block_3rd_party_scripts[ $script_key ] = $script_value;
+ }
+
+ self::register_3rd_party_script_hooks_once();
+ }
+ }
+
+ /**
+ * Register frontend/editor hooks for 3rd party scripts once.
+ *
+ * @return void
+ */
+ private static function register_3rd_party_script_hooks_once() {
+ if ( self::$block_3rd_party_hooks_registered ) {
+ return;
+ }
+
+ self::$block_3rd_party_hooks_registered = true;
+
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_all_3rd_party_scripts' ) );
+ add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_all_3rd_party_editor_scripts' ) );
+ }
+
+ /**
+ * Enqueue aggregated 3rd party scripts on the frontend.
+ *
+ * @return void
+ */
+ public static function enqueue_all_3rd_party_scripts() {
+ self::run_3rd_party_scripts_logic( false );
+ }
+
+ /**
+ * Enqueue aggregated 3rd party scripts in the block editor.
+ *
+ * @return void
+ */
+ public static function enqueue_all_3rd_party_editor_scripts() {
+ self::run_3rd_party_scripts_logic( true );
}
public function get_block_options() {
@@ -194,17 +265,23 @@
// Set blocks scripts.
$this->set_blocks_scripts();
+ $register_args = array(
+ 'api_version' => 3,
+ 'style' => $this->get_block_style(),
+ 'editor_style' => $this->get_block_editor_style(),
+ 'editor_script' => $this->get_block_editor_script(),
+ );
+
+ $block_script = $this->get_block_script();
+
+ if ( ! empty( $block_script ) ) {
+ // Frontend-only view scripts must not use legacy `script` (loads in editor too).
+ $register_args['view_script'] = $block_script;
+ }
+
register_block_type(
$this->get_blocks_namespace() . '/' . $this->get_block_name(),
- array_merge(
- array(
- 'style' => $this->get_block_style(),
- 'script' => $this->get_block_script(),
- 'editor_style' => $this->get_block_editor_style(),
- 'editor_script' => $this->get_block_editor_script(),
- ),
- $block_options
- )
+ array_merge( $register_args, $block_options )
);
}
@@ -379,88 +456,78 @@
}
public function enqueue_3rd_party_scripts() {
- $this->enqueue_3rd_party_scripts_logic();
+ self::enqueue_all_3rd_party_scripts();
}
public function enqueue_3rd_party_editor_scripts() {
- $this->enqueue_3rd_party_scripts_logic( true );
+ self::enqueue_all_3rd_party_editor_scripts();
}
public function enqueue_3rd_party_scripts_logic( $editor_mode = false ) {
- $block_3rd_party_scripts = $this->get_block_3rd_party_scripts();
+ self::run_3rd_party_scripts_logic( $editor_mode );
+ }
- if ( ! empty( $block_3rd_party_scripts ) && is_array( $block_3rd_party_scripts ) ) {
- foreach ( $block_3rd_party_scripts as $script_key => $script_value ) {
- $script_dependency = array();
- $is_script_style = false;
- $has_script_style = false;
+ /**
+ * Enqueue registered 3rd party scripts for all blocks.
+ *
+ * @param bool $editor_mode
+ *
+ * @return void
+ */
+ private static function run_3rd_party_scripts_logic( $editor_mode = false ) {
+ $block_3rd_party_scripts = self::$global_block_3rd_party_scripts;
- if ( isset( $script_value['dependency'] ) && ! empty( $script_value['dependency'] ) ) {
- $script_dependency = $script_value['dependency'];
- }
+ if ( empty( $block_3rd_party_scripts ) || ! is_array( $block_3rd_party_scripts ) ) {
+ return;
+ }
- if ( isset( $script_value['is_style'] ) ) {
- $is_script_style = $script_value['is_style'];
- }
+ if ( ! $editor_mode ) {
+ do_action( 'qi_blocks_action_additional_3rd_party_scripts' );
+ }
- if ( isset( $script_value['has_style'] ) ) {
- $has_script_style = $script_value['has_style'];
- }
+ foreach ( $block_3rd_party_scripts as $script_key => $script_value ) {
+ $script_dependency = array();
+ $is_script_style = false;
+ $has_script_style = false;
- // Check if block exist on the page and then try to load scripts.
- $additional_conditional = true;
- if ( ! $editor_mode ) {
- $additional_conditional = function_exists( 'has_block' ) && has_block( 'qi-blocks/' . $script_value['block_name'] );
-
- if ( ! $additional_conditional && function_exists( 'get_the_block_template_html' ) ) {
- $template_content = qi_blocks_get_the_block_template_html();
-
- // Check if block exist inside FSE template part.
- if ( ! empty( $template_content ) && strpos( $template_content, 'qi-block-' . $script_value['block_name'] ) !== false ) {
- $additional_conditional = true;
- }
- }
+ if ( isset( $script_value['dependency'] ) && ! empty( $script_value['dependency'] ) ) {
+ $script_dependency = $script_value['dependency'];
+ }
- // Check if block exist inside Widgets sidebar area.
- if ( ! $additional_conditional ) {
- $widgets_block = get_option( 'widget_block' );
-
- if ( ! empty( $widgets_block ) ) {
- foreach ( $widgets_block as $widget_block ) {
- if ( isset( $widget_block['content'] ) && strpos( $widget_block['content'], 'qi-blocks/' . $script_value['block_name'] ) !== false ) {
- $additional_conditional = true;
- }
- }
- }
- }
- }
+ if ( isset( $script_value['is_style'] ) ) {
+ $is_script_style = $script_value['is_style'];
+ }
- if ( isset( $script_value['block_name'] ) && $additional_conditional ) {
+ if ( isset( $script_value['has_style'] ) ) {
+ $has_script_style = $script_value['has_style'];
+ }
- if ( ! empty( $script_value['url'] ) ) {
- if ( 'core' === $script_value['url'] ) {
+ $should_enqueue = function_exists( 'qi_blocks_should_enqueue_third_party_script' )
+ ? qi_blocks_should_enqueue_third_party_script( $script_key, $script_value, $editor_mode )
+ : true;
- if ( $is_script_style ) {
- wp_enqueue_style( $script_key );
- } else {
- wp_enqueue_script( $script_key );
-
- if ( $has_script_style ) {
- wp_enqueue_style( $script_key );
- }
- }
- } else {
-
- if ( $is_script_style ) {
- wp_enqueue_style( $script_key, $script_value['url'] );
- } else {
- wp_enqueue_script( $script_key, $script_value['url'], $script_dependency, false, true );
-
- if ( $has_script_style ) {
- wp_enqueue_style( $script_key, $script_value['url'] );
- }
- }
- }
+ if ( ! $should_enqueue || empty( $script_value['url'] ) ) {
+ continue;
+ }
+
+ if ( 'core' === $script_value['url'] ) {
+ if ( $is_script_style ) {
+ wp_enqueue_style( $script_key );
+ } else {
+ wp_enqueue_script( $script_key );
+
+ if ( $has_script_style ) {
+ wp_enqueue_style( $script_key );
+ }
+ }
+ } else {
+ if ( $is_script_style ) {
+ wp_enqueue_style( $script_key, $script_value['url'] );
+ } else {
+ wp_enqueue_script( $script_key, $script_value['url'], $script_dependency, false, true );
+
+ if ( $has_script_style ) {
+ wp_enqueue_style( $script_key, $script_value['url'] );
}
}
}
--- a/qi-blocks/inc/blocks/helper.php
+++ b/qi-blocks/inc/blocks/helper.php
@@ -1,26 +1,492 @@
<?php
-if ( ! function_exists( 'qi_blocks_get_the_block_template_html' ) ) {
+if ( ! function_exists( 'qi_blocks_content_contains_block_reference' ) ) {
/**
- * Function that returns the markup for the current template.
+ * Check whether content references a Qi block by slug or namespace.
*
- * @see get_the_block_template_html()
+ * @param string $content Post content, widget content, or rendered template HTML.
+ * @param string $block_name Optional block slug without namespace.
*
- * @return string Block template markup.
+ * @return bool
*/
- function qi_blocks_get_the_block_template_html() {
- $content = get_transient( '_qi_blocks_get_the_block_template_html ');
+ function qi_blocks_content_contains_block_reference( $content, $block_name = '' ) {
+ if ( empty( $content ) || ! is_string( $content ) ) {
+ return false;
+ }
+
+ if ( '' === $block_name ) {
+ if ( false !== strpos( $content, 'qi-blocks/' ) ) {
+ return true;
+ }
+
+ return false !== strpos( $content, 'wp-block-qi-blocks-' );
+ }
+
+ if ( false !== strpos( $content, 'qi-blocks/' . $block_name ) ) {
+ return true;
+ }
+
+ return false !== strpos( $content, 'wp-block-qi-blocks-' . $block_name );
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_parsed_blocks_contain_block' ) ) {
+ /**
+ * Recursively check parsed blocks for a Qi block, including reusable blocks.
+ *
+ * @param array $blocks Parsed blocks array.
+ * @param string $block_name Optional block slug without namespace.
+ *
+ * @return bool
+ */
+ function qi_blocks_parsed_blocks_contain_block( $blocks, $block_name = '' ) {
+ if ( empty( $blocks ) || ! is_array( $blocks ) ) {
+ return false;
+ }
+
+ foreach ( $blocks as $block ) {
+ if ( empty( $block['blockName'] ) ) {
+ continue;
+ }
+
+ if ( '' === $block_name ) {
+ if ( 0 === strpos( $block['blockName'], 'qi-blocks/' ) ) {
+ return true;
+ }
+ } elseif ( 'qi-blocks/' . $block_name === $block['blockName'] ) {
+ return true;
+ }
+
+ if ( 'core/block' === $block['blockName'] && ! empty( $block['attrs']['ref'] ) ) {
+ $reusable_post = get_post( (int) $block['attrs']['ref'] );
+
+ if ( $reusable_post && ! empty( $reusable_post->post_content ) ) {
+ if ( qi_blocks_content_contains_block_reference( $reusable_post->post_content, $block_name ) ) {
+ return true;
+ }
+
+ if ( function_exists( 'parse_blocks' ) && qi_blocks_parsed_blocks_contain_block( parse_blocks( $reusable_post->post_content ), $block_name ) ) {
+ return true;
+ }
+ }
+ }
+
+ if ( 'core/template-part' === $block['blockName'] && ! empty( $block['attrs']['slug'] ) && function_exists( 'get_block_template' ) ) {
+ $template_theme = ! empty( $block['attrs']['theme'] ) ? $block['attrs']['theme'] : get_stylesheet();
+ $template_part = get_block_template( $template_theme . '//' . $block['attrs']['slug'], 'wp_template_part' );
+
+ if ( $template_part && ! empty( $template_part->content ) ) {
+ if ( qi_blocks_content_contains_block_reference( $template_part->content, $block_name ) ) {
+ return true;
+ }
+
+ if ( function_exists( 'parse_blocks' ) && qi_blocks_parsed_blocks_contain_block( parse_blocks( $template_part->content ), $block_name ) ) {
+ return true;
+ }
+ }
+ }
+
+ if ( ! empty( $block['innerBlocks'] ) && qi_blocks_parsed_blocks_contain_block( $block['innerBlocks'], $block_name ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_page_contains_qi_blocks' ) ) {
+ /**
+ * Detect whether the current frontend request needs Qi Blocks assets.
+ *
+ * @return bool
+ */
+ function qi_blocks_page_contains_qi_blocks() {
+ static $contains = null;
+ static $resolving = false;
+
+ if ( null !== $contains ) {
+ return $contains;
+ }
+
+ if ( $resolving ) {
+ return false;
+ }
+
+ $resolving = true;
+ $contains = false;
+
+ if ( is_admin() ) {
+ $resolving = false;
+
+ return $contains;
+ }
+
+ $page_id = get_queried_object_id();
+
+ if ( $page_id ) {
+ $post_content = get_post_field( 'post_content', $page_id );
+
+ if ( qi_blocks_content_contains_block_reference( $post_content ) ) {
+ $contains = true;
+ $resolving = false;
+
+ return $contains;
+ }
+
+ if ( function_exists( 'parse_blocks' ) && qi_blocks_parsed_blocks_contain_block( parse_blocks( $post_content ) ) ) {
+ $contains = true;
+ $resolving = false;
+
+ return $contains;
+ }
+ }
+
+ if ( ! $contains && function_exists( 'qi_blocks_get_block_template_content' ) && qi_blocks_should_resolve_block_template_html() ) {
+ $template_content = qi_blocks_get_block_template_content();
+
+ if ( qi_blocks_content_contains_block_reference( $template_content ) ) {
+ $contains = true;
+ } elseif ( function_exists( 'parse_blocks' ) && qi_blocks_parsed_blocks_contain_block( parse_blocks( $template_content ) ) ) {
+ $contains = true;
+ }
+ }
+
+ if ( ! $contains ) {
+ $widgets_block = get_option( 'widget_block' );
+
+ if ( ! empty( $widgets_block ) && is_array( $widgets_block ) ) {
+ foreach ( $widgets_block as $widget_block ) {
+ if ( isset( $widget_block['content'] ) && qi_blocks_content_contains_block_reference( $widget_block['content'] ) ) {
+ $contains = true;
+ break;
+ }
+ }
+ }
+ }
+
+ $resolving = false;
+
+ return $contains;
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_should_load_frontend_assets' ) ) {
+ /**
+ * Whether Qi Blocks frontend assets should be enqueued.
+ *
+ * @return bool
+ */
+ function qi_blocks_should_load_frontend_assets() {
+ return (bool) apply_filters( 'qi_blocks_should_load_frontend_assets', qi_blocks_page_contains_qi_blocks() );
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_is_block_present_on_page' ) ) {
+ /**
+ * Check if a specific Qi block is present in post content, templates, or widgets.
+ *
+ * @param string $block_name Block slug without namespace.
+ *
+ * @return bool
+ */
+ function qi_blocks_is_block_present_on_page( $block_name ) {
+ static $presence_cache = array();
+
+ if ( isset( $presence_cache[ $block_name ] ) ) {
+ return $presence_cache[ $block_name ];
+ }
- if ( empty( $content ) ) {
- $content = get_the_block_template_html();
+ $is_present = function_exists( 'has_block' ) && has_block( 'qi-blocks/' . $block_name );
- set_transient( '_qi_blocks_get_the_block_template_html', $content, 5 );
+ if ( ! $is_present ) {
+ $page_id = get_queried_object_id();
+
+ if ( $page_id ) {
+ $post_content = get_post_field( 'post_content', $page_id );
+
+ if ( qi_blocks_content_contains_block_reference( $post_content, $block_name ) ) {
+ $is_present = true;
+ } elseif ( function_exists( 'parse_blocks' ) && qi_blocks_parsed_blocks_contain_block( parse_blocks( $post_content ), $block_name ) ) {
+ $is_present = true;
+ }
+ }
+ }
+
+ if ( ! $is_present && function_exists( 'qi_blocks_get_block_template_content' ) && qi_blocks_should_resolve_block_template_html() ) {
+ $template_content = qi_blocks_get_block_template_content();
+
+ if ( qi_blocks_content_contains_block_reference( $template_content, $block_name ) ) {
+ $is_present = true;
+ } elseif ( function_exists( 'parse_blocks' ) && qi_blocks_parsed_blocks_contain_block( parse_blocks( $template_content ), $block_name ) ) {
+ $is_present = true;
+ }
+ }
+
+ if ( ! $is_present ) {
+ static $widgets_block = null;
+
+ if ( null === $widgets_block ) {
+ $widgets_block = get_option( 'widget_block' );
+ }
+
+ if ( ! empty( $widgets_block ) && is_array( $widgets_block ) ) {
+ foreach ( $widgets_block as $widget_block ) {
+ if ( isset( $widget_block['content'] ) && qi_blocks_content_contains_block_reference( $widget_block['content'], $block_name ) ) {
+ $is_present = true;
+ break;
+ }
+ }
+ }
}
- return $content;
+ $presence_cache[ $block_name ] = $is_present;
+
+ return $is_present;
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_get_slider_block_names' ) ) {
+ /**
+ * Block slugs that require the Swiper library on the frontend.
+ *
+ * @return array
+ */
+ function qi_blocks_get_slider_block_names() {
+ return (array) apply_filters(
+ 'qi_blocks_filter_slider_block_names',
+ array(
+ 'image-slider',
+ 'blog-slider',
+ 'product-slider',
+ 'clients-slider',
+ 'testimonials-slider',
+ 'device-slider',
+ 'device-carousel',
+ 'preview-slider',
+ 'slider-switch',
+ )
+ );
}
}
+if ( ! function_exists( 'qi_blocks_parsed_blocks_contain_slider_block' ) ) {
+ /**
+ * Recursively check parsed blocks for any Qi slider block.
+ *
+ * @param array $blocks Parsed blocks array.
+ *
+ * @return bool
+ */
+ function qi_blocks_parsed_blocks_contain_slider_block( $blocks ) {
+ foreach ( qi_blocks_get_slider_block_names() as $slider_block_name ) {
+ if ( qi_blocks_parsed_blocks_contain_block( $blocks, $slider_block_name ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_content_contains_swiper_markup' ) ) {
+ /**
+ * Whether content includes rendered Qi slider markup.
+ *
+ * @param string $content Optional content string.
+ *
+ * @return bool
+ */
+ function qi_blocks_content_contains_swiper_markup( $content = '' ) {
+ if ( is_string( $content ) && '' !== $content ) {
+ return false !== strpos( $content, 'qodef-block-swiper' );
+ }
+
+ $page_id = get_queried_object_id();
+
+ if ( $page_id ) {
+ $post_content = get_post_field( 'post_content', $page_id );
+
+ if ( false !== strpos( $post_content, 'qodef-block-swiper' ) ) {
+ return true;
+ }
+ }
+
+ if ( function_exists( 'qi_blocks_get_block_template_content' ) && qi_blocks_should_resolve_block_template_html() ) {
+ $template_content = qi_blocks_get_block_template_content();
+
+ foreach ( qi_blocks_get_slider_block_names() as $slider_block_name ) {
+ if ( qi_blocks_content_contains_block_reference( $template_content, $slider_block_name ) ) {
+ return true;
+ }
+ }
+
+ if ( function_exists( 'parse_blocks' ) && qi_blocks_parsed_blocks_contain_slider_block( parse_blocks( $template_content ) ) ) {
+ return true;
+ }
+ }
+
+ $widgets_block = get_option( 'widget_block' );
+
+ if ( ! empty( $widgets_block ) && is_array( $widgets_block ) ) {
+ foreach ( $widgets_block as $widget_block ) {
+ if ( isset( $widget_block['content'] ) && false !== strpos( $widget_block['content'], 'qodef-block-swiper' ) ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_page_needs_swiper' ) ) {
+ /**
+ * Whether the current request needs the Swiper library.
+ *
+ * @return bool
+ */
+ function qi_blocks_page_needs_swiper() {
+ static $needs_swiper = null;
+
+ if ( null !== $needs_swiper ) {
+ return $needs_swiper;
+ }
+
+ $needs_swiper = false;
+
+ foreach ( qi_blocks_get_slider_block_names() as $slider_block_name ) {
+ if ( qi_blocks_is_block_present_on_page( $slider_block_name ) ) {
+ $needs_swiper = true;
+
+ return $needs_swiper;
+ }
+ }
+
+ if ( qi_blocks_content_contains_swiper_markup() ) {
+ $needs_swiper = true;
+ }
+
+ return $needs_swiper;
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_should_enqueue_third_party_script' ) ) {
+ /**
+ * Whether a shared 3rd party script should be enqueued.
+ *
+ * @param string $script_key Script handle.
+ * @param array $script_value Script config.
+ * @param bool $editor_mode Whether the editor is loading assets.
+ *
+ * @return bool
+ */
+ function qi_blocks_should_enqueue_third_party_script( $script_key, $script_value, $editor_mode = false ) {
+ if ( $editor_mode ) {
+ return true;
+ }
+
+ if ( 'swiper' === $script_key ) {
+ return qi_blocks_page_needs_swiper();
+ }
+
+ $block_names = array();
+
+ if ( ! empty( $script_value['block_names'] ) && is_array( $script_value['block_names'] ) ) {
+ $block_names = $script_value['block_names'];
+ } elseif ( ! empty( $script_value['block_name'] ) ) {
+ $block_names = array( $script_value['block_name'] );
+ }
+
+ if ( empty( $block_names ) ) {
+ return true;
+ }
+
+ foreach ( $block_names as $block_name ) {
+ if ( qi_blocks_is_block_present_on_page( $block_name ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_should_resolve_block_template_html' ) ) {
+ /**
+ * Whether block template content should be resolved for frontend asset loading.
+ *
+ * @return bool
+ */
+ function qi_blocks_should_resolve_block_template_html() {
+ $should_resolve = function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
+
+ return (bool) apply_filters( 'qi_blocks_should_resolve_block_template_html', $should_resolve );
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_get_block_template_content' ) ) {
+ /**
+ * Return raw block template content for the current request.
+ *
+ * Does not render blocks — safe to call during wp_enqueue_scripts.
+ *
+ * @return string
+ */
+ function qi_blocks_get_block_template_content() {
+ static $request_cache = null;
+
+ if ( null !== $request_cache ) {
+ return $request_cache;
+ }
+
+ if ( ! qi_blocks_should_resolve_block_template_html() ) {
+ $request_cache = '';
+
+ return $request_cache;
+ }
+
+ global $_wp_current_template_content;
+
+ if ( empty( $_wp_current_template_content ) || ! is_string( $_wp_current_template_content ) ) {
+ $request_cache = '';
+
+ return $request_cache;
+ }
+
+ $request_cache = $_wp_current_template_content;
+
+ return $request_cache;
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_get_the_block_template_html' ) ) {
+ /**
+ * Backward-compatible alias for raw template content used in asset detection.
+ *
+ * @deprecated Never calls get_the_block_template_html() — rendering blocks during asset detection caused duplicate script loads in the Site Editor.
+ *
+ * @return string Raw block template content.
+ */
+ function qi_blocks_get_the_block_template_html() {
+ return qi_blocks_get_block_template_content();
+ }
+}
+
+if ( ! function_exists( 'qi_blocks_cleanup_legacy_block_template_transient' ) ) {
+ /**
+ * Remove legacy transient key that contained a trailing space.
+ *
+ * @return void
+ */
+ function qi_blocks_cleanup_legacy_block_template_transient() {
+ delete_transient( '_qi_blocks_get_the_block_template_html' );
+ delete_transient( '_qi_blocks_get_the_block_template_html ' );
+ }
+
+ add_action( 'init', 'qi_blocks_cleanup_legacy_block_template_transient', 1 );
+}
+
if ( ! function_exists( 'qi_blocks_get_premium_blocks_list' ) ) {
/**
* Function that return premium blocks list
--- a/qi-blocks/inc/libraries/svg-sanitizer/Exceptions/NestingException.php
+++ b/qi-blocks/inc/libraries/svg-sanitizer/Exceptions/NestingException.php
@@ -18,7 +18,7 @@
* @param Exception|null $previous
* @param DOMElement|null $element
*/
- public function __construct($message = "", $code = 0, Exception $previous = null, DOMElement $element = null)
+ public function __construct($message = "", $code = 0, ?Exception $previous = null, ?DOMElement $element = null)
{
$this->element = $element;
parent::__construct($message, $code, $previous);
--- a/qi-blocks/inc/libraries/svg-sanitizer/Sanitizer.php
+++ b/qi-blocks/inc/libraries/svg-sanitizer/Sanitizer.php
@@ -220,8 +220,13 @@
return '';
}
- // Strip php tags
- $dirty = preg_replace('/<?(=|php)(.+?)?>/i', '', $dirty);
+ do {
+ /*
+ * recursively remove php tags because they can be hidden inside tags
+ * i.e. <?p<?php test?>hp echo . ' danger! ';?>
+ */
+ $dirty = preg_replace('/<?(=|php)(.+?)?>/i', '', $dirty);
+ } while (preg_match('/<?(=|php)(.+?)?>/i', $dirty) != 0);
$this->resetInternal();
$this->setUpBefore();
@@ -230,6 +235,7 @@
// If we couldn't parse the XML then we go no further. Reset and return false
if (!$loaded) {
+ $this->xmlIssues = self::getXmlErrors();
$this->resetAfter();
return false;
}
@@ -363,7 +369,7 @@
$breaksOutOfForeignContent = false;
for ($x = $currentElement->attributes->length - 1; $x >= 0; $x--) {
// get attribute name
- $attrName = $currentElement->attributes->item( $x )->name;
+ $attrName = $currentElement->attributes->item( $x )->nodeName;
if (in_array(strtolower($attrName), ['face', 'color', 'size'])) {
$breaksOutOfForeignContent = true;
@@ -398,7 +404,7 @@
{
for ($x = $element->attributes->length - 1; $x >= 0; $x--) {
// get attribute name
- $attrName = $element->attributes->item($x)->name;
+ $attrName = $element->attributes->item($x)->nodeName;
// Remove attribute if not in whitelist
if (!in_array(strtolower($attrName), $this->allowedAttrs) && !$this->isAriaAttribute(strtolower($attrName)) && !$this->isDataAttribute(strtolower($attrName))) {
@@ -415,7 +421,7 @@
* Such as xlink:href when the xlink namespace isn't imported.
* We have to do this as the link is still ran in this case.
*/
- if (false !== strpos($attrName, 'href')) {
+ if (false !== stripos($attrName, 'href')) {
$href = $element->getAttribute($attrName);
if (false === $this->isHrefSafeValue($href)) {
$element->removeAttribute($attrName);
@@ -447,14 +453,17 @@
*/
protected function cleanXlinkHrefs(DOMElement $element)
{
- $xlinks = $element->getAttributeNS('http://www.w3.org/1999/xlink', 'href');
- if (false === $this->isHrefSafeValue($xlinks)) {
- $element->removeAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
- $this->xmlIssues[] = array(
- 'message' => 'Suspicious attribute 'href'',
- 'line' => $element->getLineNo(),
- );
+ foreach ($element->attributes as $attribute) {
+ // remove attributes with unexpected namespace prefix, e.g. `XLinK:href` (instead of `xlink:href`)
+ if ($attribute->prefix === '' && strtolower($attribute->nodeName) === 'xlink:href') {
+ $element->removeAttribute($attribute->nodeName);
+ $this->xmlIssues[] = array(
+ 'message' => sprintf('Unexpected attribute '%s'', $attribute->nodeName),
+ 'line' => $element->getLineNo(),
+ );
+ }
}
+ $this->cleanHrefAttributes($element, 'xlink');
}
/**
@@ -464,13 +473,33 @@
*/
protected function cleanHrefs(DOMElement $element)
{
- $href = $element->getAttribute('href');
- if (false === $this->isHrefSafeValue($href)) {
- $element->removeAttribute('href');
- $this->xmlIssues[] = array(
- 'message' => 'Suspicious attribute 'href'',
- 'line' => $element->getLineNo(),
- );
+ $this->cleanHrefAttributes($element);
+ }
+
+ protected function cleanHrefAttributes(DOMElement $element, string $prefix = ''): void
+ {
+ $relevantAttributes = array_filter(
+ iterator_to_array($element->attributes),
+ static function (DOMAttr $attr) use ($prefix) {
+ return strtolower($attr->name) === 'href' && strtolower($attr->prefix) === $prefix;
+ }
+ );
+ foreach ($relevantAttributes as $attribute) {
+ if (!$this->isHrefSafeValue($attribute->value)) {
+ $element->removeAttribute($attribute->nodeName);
+ $this->xmlIssues[] = array(
+ 'message' => sprintf('Suspicious attribute '%s'', $attribute->nodeName),
+ 'line' => $element->getLineNo(),
+ );
+ continue;
+ }
+ // in case the attribute name is `HrEf`/`xlink:HrEf`, adjust it to `href`/`xlink:href`
+ if (!in_array($attribute->nodeName, $this->allowedAttrs, true)
+ && in_array(strtolower($attribute->nodeName), $this->allowedAttrs, true)
+ ) {
+ $element->removeAttribute($attribute->nodeName);
+ $element->setAttribute(strtolower($attribute->nodeName), $attribute->value);
+ }
}
}
@@ -698,4 +727,21 @@
}
}
}
+
+ /**
+ * Retrieve array of errors
+ * @return array
+ */
+ private static function getXmlErrors()
+ {
+ $errors = [];
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = [
+ 'message' => trim($error->message),
+ 'line' => $error->line,
+ ];
+ }
+
+ return $errors;
+ }
}
--- a/qi-blocks/inc/libraries/svg-sanitizer/data/AllowedAttributes.php
+++ b/qi-blocks/inc/libraries/svg-sanitizer/data/AllowedAttributes.php
@@ -143,6 +143,7 @@
'direction',
'display',
'divisor',
+ 'dominant-baseline',
'dur',
'edgemode',
'elevation',
--- a/qi-blocks/inc/slider/helper.php
+++ b/qi-blocks/inc/slider/helper.php
@@ -42,6 +42,23 @@
add_filter( 'qi_blocks_filter_block_style_dependency', 'qi_blocks_set_slider_style_as_block_style_dependency', 5 );
}
+if ( ! function_exists( 'qi_blocks_set_swiper_script_as_editor_dependency' ) ) {
+ /**
+ * Ensure Swiper is loaded before editor scripts that initialize sliders.
+ *
+ * @param array $script_dependency
+ *
+ * @return array
+ */
+ function qi_blocks_set_swiper_script_as_editor_dependency( $script_dependency ) {
+ $script_dependency[] = 'swiper';
+
+ return $script_dependency;
+ }
+
+ add_filter( 'qi_blocks_filter_main_editor_dependencies', 'qi_blocks_set_swiper_script_as_editor_dependency', 15 );
+}
+
if ( ! function_exists( 'qi_blocks_get_block_slider_attributes' ) ) {
/**
* Function that return block slider attributes