文件操作 - api.php
返回文件管理
返回主菜单
删除本文件
文件: /storage/v12552/stanleysteamer/public_html/wp-content/themes/bricks/includes/api.php
编辑文件内容
<?php namespace Bricks; if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly class Api { const API_NAMESPACE = 'bricks/v1'; public static $request_data = []; // Store request data from the API request (@since 2.2) public static $query_element_id = null; // Holds the current query element ID during a REST API request (@since 2.1) public static $active_templates = []; // Holds the current active templates during a REST API request (@since 2.2) /** * WordPress REST API help docs: * * https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/ * https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-responses/ */ public function __construct() { add_action( 'rest_api_init', [ $this, 'rest_api_init_custom_endpoints' ] ); } /** * Custom REST API endpoints */ public function rest_api_init_custom_endpoints() { // Server-side render (SSR) for builder elements via window.fetch API requests register_rest_route( self::API_NAMESPACE, 'render_element', [ 'methods' => 'POST', 'callback' => [ $this, 'render_element' ], 'permission_callback' => [ $this, 'render_element_permissions_check' ], ] ); // Get all templates data (templates, authors, bundles, tags etc.) register_rest_route( self::API_NAMESPACE, '/get-templates-data/', [ 'methods' => 'GET', 'callback' => [ $this, 'get_templates_data' ], 'permission_callback' => '__return_true', ] ); register_rest_route( self::API_NAMESPACE, '/get-templates/', [ 'methods' => 'GET', 'callback' => [ $this, 'get_templates' ], 'permission_callback' => '__return_true', ] ); // Get individual template by ID register_rest_route( self::API_NAMESPACE, '/get-templates/(?P<args>[a-zA-Z0-9-=&]+)', [ 'methods' => 'GET', 'callback' => [ $this, 'get_templates' ], 'permission_callback' => '__return_true', 'args' => [ 'args' => [ 'required' => true ], ], ] ); register_rest_route( self::API_NAMESPACE, '/get-template-authors/', [ 'methods' => 'GET', 'callback' => [ $this, 'get_template_authors' ], 'permission_callback' => '__return_true', ] ); register_rest_route( self::API_NAMESPACE, '/get-template-bundles/', [ 'methods' => 'GET', 'callback' => [ $this, 'get_template_bundles' ], 'permission_callback' => '__return_true', ] ); register_rest_route( self::API_NAMESPACE, '/get-template-tags/', [ 'methods' => 'GET', 'callback' => [ $this, 'get_template_tags' ], 'permission_callback' => '__return_true', ] ); /** * Query loop: Infinite scroll * * @since 1.5 */ register_rest_route( self::API_NAMESPACE, 'load_query_page', [ 'methods' => 'POST', 'callback' => [ $this, 'render_query_page' ], 'permission_callback' => [ $this, 'render_query_page_permissions_check' ], ] ); /** * Ajax Popup * * @since 1.9.4 */ register_rest_route( self::API_NAMESPACE, 'load_popup_content', [ 'methods' => 'POST', 'callback' => [ $this, 'render_popup_content' ], 'permission_callback' => [ $this, 'render_popup_content_permissions_check' ], ] ); /** * Query loop: Query result * * For load more, AJAX pagination, sort, filter, live search. * * @since 1.9.6 */ register_rest_route( self::API_NAMESPACE, 'query_result', [ 'methods' => 'POST', 'callback' => [ $this, 'render_query_result' ], 'permission_callback' => [ $this, 'render_query_result_permissions_check' ], ] ); /** * Get global classes categorized by their usage across the site * * @since 1.12 */ register_rest_route( self::API_NAMESPACE, '/get-global-classes-site-usage', [ 'methods' => 'GET', 'callback' => [ $this, 'get_global_classes_site_usage' ], 'permission_callback' => [ $this, 'get_global_classes_site_usage_permissions_check' ], ] ); } /** * Return element HTML retrieved via Fetch API * * @since 1.5 */ public static function render_element( $request ) { $data = $request->get_json_params(); $post_id = $data['postId'] ?? false; $element = $data['element'] ?? []; $element_name = $element['name'] ?? ''; $element_settings = $element['settings'] ?? ''; if ( $post_id ) { // Set context in API endpoint (@since 1.12.2) global $wp_query; global $post; $post = get_post( $post_id ); setup_postdata( $post ); /** * Set necessary global variables so we can use get_queried_object(), get_the_ID() etc. */ if ( $post && ! is_wp_error( $post ) ) { $wp_query->queried_object = $post; $wp_query->queried_object_id = $post->ID; $wp_query->is_singular = true; $wp_query->post_type = $post->post_type; if ( is_page( $post->ID ) ) { $wp_query->is_page = true; } else { $wp_query->is_single = true; } } Database::set_page_data( $post_id ); } // Include WooCommerce frontend classes and hooks to enable the WooCommerce element preview inside the builder (since 1.5) if ( Woocommerce::$is_active ) { WC()->frontend_includes(); Woocommerce_Helpers::maybe_load_cart(); } // Get rendered element HTML $html = Ajax::render_element( $data ); // Prepare response $response = [ 'html' => $html ]; // Template element (send template elements to run template element scripts on the canvas) if ( $element_name === 'template' ) { $template_id = $element_settings['template'] ?? false; if ( $template_id ) { $additional_data = Element_Template::get_builder_call_additional_data( $template_id ); $response = array_merge( $response, $additional_data ); } } return [ 'data' => $response ]; } /** * Element render permission check * * @since 1.5 */ public function render_element_permissions_check( $request ) { $data = $request->get_json_params(); if ( empty( $data['postId'] ) || empty( $data['element'] ) || empty( $data['nonce'] ) ) { return new \WP_Error( 'bricks_api_missing', __( 'Missing parameters' ), [ 'status' => 400 ] ); } // Return: Current user can not access builder // Provide postId or current_user_can_use_builder() unable to get correct post type (#86c64u37m; @since 2.2) if ( ! Capabilities::current_user_can_use_builder( $data['postId'] ?? 0 ) ) { return new \WP_Error( 'rest_current_user_can_not_use_builder', __( 'Permission error' ), [ 'status' => 403 ] ); } return true; } /** * Return all templates data in one call (templates, authors, bundles, tags, theme style) * * @param array $data * @return array * * @since 1.0 */ public function get_templates_data( $data ) { $templates_args = $data['args'] ?? []; // STEP: Get templates metadata or all data $templates = $this->get_templates( $templates_args ); // STEP: Check for template error if ( isset( $templates['error'] ) ) { return $templates; } $theme_styles = get_option( BRICKS_DB_THEME_STYLES, false ); $global_classes = get_option( BRICKS_DB_GLOBAL_CLASSES, [] ); $global_variables = get_option( BRICKS_DB_GLOBAL_VARIABLES, [] ); $color_palette = get_option( BRICKS_DB_COLOR_PALETTE, [] ); // @since 1.12 // STEP: Add all active theme styles to template data to import when inserting a template foreach ( $templates as $index => $template ) { /** * Provide only most-specific theme style for template import * * @since 2.0: Provide all active theme styles for template import (regardless if 'themeStylesLoadingMethod' Bricks setting is enabled on the site) */ $theme_style_ids = Theme_Styles::set_active_style( $template['id'], true ); if ( is_array( $theme_style_ids ) && count( $theme_style_ids ) ) { foreach ( $theme_style_ids as $theme_style_id ) { // Get theme style by ID $theme_style = $theme_styles[ $theme_style_id ] ?? false; // Skip if theme style not found if ( ! $theme_style ) { continue; } // Remove theme style conditions unset( $theme_style['settings']['conditions'] ); if ( ! isset( $templates[ $index ]['themeStyles'] ) ) { $templates[ $index ]['themeStyles'] = []; } /** * NOTE: @pre 2.0 we passed a single 'themeStyle' * * @since 2.0 we pass an array of all active 'themeStyles' */ $theme_style['id'] = $theme_style_id; $templates[ $index ]['themeStyles'][] = $theme_style; } } /** * Loop over all template elements to add 'global_classes' data to remote template data * * To import global classes when importing remote template locally. * * @since 1.5 */ if ( count( $global_classes ) ) { $template_classes = []; $template_elements = []; if ( ! empty( $template['content'] ) && is_array( $template['content'] ) ) { $template_elements = $template['content']; } elseif ( ! empty( $template['header'] ) && is_array( $template['header'] ) ) { $template_elements = $template['header']; } elseif ( ! empty( $template['footer'] ) && is_array( $template['footer'] ) ) { $template_elements = $template['footer']; } foreach ( $template_elements as $element ) { if ( ! empty( $element['settings']['_cssGlobalClasses'] ) ) { $template_classes = array_unique( array_merge( $template_classes, $element['settings']['_cssGlobalClasses'] ) ); } } if ( count( $template_classes ) ) { $templates[ $index ]['global_classes'] = []; foreach ( $template_classes as $template_class ) { foreach ( $global_classes as $global_class ) { if ( $global_class['id'] === $template_class ) { // Add category metadata to individual class before adding to template (@since 1.12.2) $global_class = Helpers::add_category_metadata_to_classes( [ $global_class ] )[0]; $templates[ $index ]['global_classes'][] = $global_class; } } } } } } // Return all templates data $templates_data = [ 'timestamp' => current_time( 'timestamp' ), 'date' => current_time( get_option( 'date_format' ) . ' (' . get_option( 'time_format' ) . ')' ), 'templates' => $templates, 'authors' => Templates::get_template_authors(), 'bundles' => Templates::get_template_bundles(), 'tags' => Templates::get_template_tags(), 'globalVariables' => $global_variables, // @since 1.9.8 'colorPalette' => $color_palette, // To allowing importing all color palettes found in the inserted template (@since 1.12) 'get' => $_GET, // Pass URL params to perform additional checks (e.g. 'password' as license key, etc.) ]; $templates_data = apply_filters( 'bricks/api/get_templates_data', $templates_data ); // Remove 'get' data (to avoid storing it in database) unset( $templates_data['get'] ); return $templates_data; } /** * Return templates array OR specific template by array index * * @since 1.0 * * @param array $data * * @return array */ public function get_templates( $data ) { $parameters = $_GET; $templates_response = Templates::can_get_templates( $parameters ); // Check for templates error (no site/password etc. provided) if ( isset( $templates_response['error'] ) ) { return $templates_response; } $templates_args = $data['args'] ?? []; // Add remote_request flag (@since 1.12.2) $templates_args['remote_request'] = true; // Merge $parameters with $templates_response args $templates_args = array_merge( $templates_args, $templates_response ); $templates = Templates::get_templates( $templates_args ); return $templates; } /** * Get API endpoint * * Use /api to get Bricks Community Templates * Default: Use /wp-json (= default WP REST API prefix) * * @param string $endpoint API endpoint. * @param string $base_url Base URL. * * @since 1.0 * * @return string */ public static function get_endpoint( $endpoint = 'get-templates', $base_url = BRICKS_REMOTE_URL ) { $api_prefix = $base_url === BRICKS_REMOTE_URL ? 'api' : rest_get_url_prefix(); return trailingslashit( $base_url ) . trailingslashit( $api_prefix ) . trailingslashit( self::API_NAMESPACE ) . $endpoint; } /** * Get the Bricks REST API url * * @since 1.5 * * @return string */ public static function get_rest_api_url() { return trailingslashit( get_rest_url( null, '/' . self::API_NAMESPACE ) ); } /** * Sanitize REST request data used across render callbacks. (#86c21d4m0; @since 2.3.8) * * @param array $request_data Request data. * * @return array * * @since 2.3.8 */ public static function sanitize_request_data( $request_data ) { if ( isset( $request_data['baseUrl'] ) ) { $request_data['baseUrl'] = is_string( $request_data['baseUrl'] ) ? esc_url_raw( $request_data['baseUrl'] ) : ''; } if ( isset( $request_data['urlParams'] ) ) { $request_data['urlParams'] = is_array( $request_data['urlParams'] ) ? self::sanitize_url_params( $request_data['urlParams'] ) : []; } return $request_data; } /** * Sanitize URL parameter values from REST request data. (#86c21d4m0; @since 2.3.8) * * @param array $url_params URL parameters. * * @return array * * @since 2.3.8 */ public static function sanitize_url_params( $url_params ) { $sanitized_url_params = []; foreach ( $url_params as $key => $value ) { $sanitized_url_params[ $key ] = self::sanitize_url_param_value( $value ); } return $sanitized_url_params; } /** * Sanitize a single URL parameter value. (#86c21d4m0; @since 2.3.8) * * @param mixed $value URL parameter value. * * @return string|array * * @since 2.3.8 */ public static function sanitize_url_param_value( $value ) { if ( is_array( $value ) ) { return self::sanitize_url_params( $value ); } if ( ! is_scalar( $value ) ) { return ''; } $value = (string) $value; return filter_var( $value, FILTER_VALIDATE_URL ) ? esc_url_raw( $value ) : sanitize_text_field( $value ); } /** * Check if current endpoint is Bricks API endpoint * * @param string $endpoint E.g. 'render_element' or 'load_query_page' for our infinite scroll. * * @since 1.8.1 * * @return bool */ public static function is_current_endpoint( $endpoint ) { if ( ! $endpoint ) { return false; } return self::is_bricks_rest_request( $endpoint ); } /** * Check if current request is a Bricks REST API request * * Works reliably during init hook before REST API is fully initialized. * * @since 2.0 * * @return bool */ public static function is_bricks_rest_request( $endpoint = '' ) { // Build the namespace pattern $namespace_pattern = '/' . self::API_NAMESPACE . '/'; $endpoint_pattern = $endpoint ? $namespace_pattern . $endpoint : $namespace_pattern; // Method 1: Check if REST_REQUEST constant is defined and check for Bricks namespace if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { global $wp; $current_rest_route = isset( $wp->query_vars['rest_route'] ) ? $wp->query_vars['rest_route'] : ''; if ( $current_rest_route ) { if ( $endpoint ) { return $current_rest_route === $endpoint_pattern; } else { return strpos( $current_rest_route, $namespace_pattern ) === 0; } } } // Method 2: Check REQUEST_URI for Bricks namespace (works during init hook) if ( isset( $_SERVER['REQUEST_URI'] ) ) { $request_uri = $_SERVER['REQUEST_URI']; $rest_prefix = rest_get_url_prefix(); // Usually 'wp-json' // Check for pretty permalinks pattern $full_pattern = '/' . $rest_prefix . $endpoint_pattern; if ( strpos( $request_uri, $full_pattern ) !== false ) { return true; } // Check for non-pretty permalinks pattern: ?rest_route= if ( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] ) { parse_str( $_SERVER['QUERY_STRING'], $query_params ); if ( isset( $query_params['rest_route'] ) ) { $rest_route = $query_params['rest_route']; if ( $endpoint ) { return $rest_route === $endpoint_pattern; } else { return strpos( $rest_route, $namespace_pattern ) === 0; } } } } // Method 3: Check global $wp for Bricks REST route (fallback) global $wp; if ( isset( $wp->query_vars['rest_route'] ) ) { $current_rest_route = $wp->query_vars['rest_route']; if ( $endpoint ) { return $current_rest_route === $endpoint_pattern; } else { return strpos( $current_rest_route, $namespace_pattern ) === 0; } } return false; } /** * Get template authors * * @since 1.0 * * @return array */ public static function get_template_authors() { return Templates::get_template_authors(); } /** * Get template bundles * * @since 1.0 * * @return array */ public static function get_template_bundles() { return Templates::get_template_bundles(); } /** * Get template tags * * @since 1.0 * * @return array */ public static function get_template_tags() { return Templates::get_template_tags(); } /** * Get news feed * * NOTE: Not in use. * * @return array */ public static function get_feed() { $remote_base_url = BRICKS_REMOTE_URL; $feed_url = trailingslashit( $remote_base_url ) . trailingslashit( rest_get_url_prefix() ) . trailingslashit( self::API_NAMESPACE ) . trailingslashit( 'feed' ); $response = Helpers::remote_get( $feed_url ); if ( is_wp_error( $response ) ) { return []; } else { return json_decode( wp_remote_retrieve_body( $response ), true ); } } /** * Query loop: Infinite scroll permissions callback * * @since 1.5 */ public function render_query_page_permissions_check( $request ) { $data = $request->get_json_params(); if ( empty( $data['queryElementId'] ) || empty( $data['nonce'] ) || empty( $data['page'] ) ) { return new \WP_Error( 'bricks_api_missing', __( 'Missing parameters' ), [ 'status' => 400 ] ); } $result = wp_verify_nonce( $data['nonce'], 'bricks-nonce' ); if ( $result === false ) { return new \WP_Error( 'rest_cookie_invalid_nonce', __( 'Bricks cookie check failed' ), [ 'status' => 403 ] ); } return true; } /** * Query loop: Infinite scroll callback * Or AJAX pagination (@since 1.12.2) * * @since 1.5 */ public function render_query_page( $request ) { $request_data = self::$request_data = self::sanitize_request_data( $request->get_json_params() ); $query_element_id = $ori_query_element_id = $request_data['queryElementId']; $post_id = $request_data['postId']; $page = $request_data['page']; $query_vars = json_decode( $request_data['queryVars'], true ); $language = isset( $request_data['lang'] ) ? sanitize_key( $request_data['lang'] ) : false; $pagination_id = isset( $request_data['paginationId'] ) ? sanitize_key( $request_data['paginationId'] ) : false; $base_url = $request_data['baseUrl'] ?? ''; $main_query_id = isset( $request_data['mainQueryId'] ) ? sanitize_text_field( $request_data['mainQueryId'] ) : false; $search_template_id = isset( $request_data['activeSearchTemplate'] ) ? absint( $request_data['activeSearchTemplate'] ) : 0; // Set current language (@since 1.9.9) if ( $language ) { Database::set_page_data_language( $language ); } // Set main query ID (@since 2.0) if ( $main_query_id ) { Database::$main_query_id = $main_query_id; } // Set active search template ID to apply search criteria setting (@since 2.2) if ( $search_template_id ) { self::$active_templates['search'] = $search_template_id; } // Set post_id for use in prepare_query_vars_from_settings Database::$page_data['preview_or_post_id'] = $post_id; // Allow addtional actions for custom code. WPML (@since 1.12.2) do_action( 'bricks/render_query_page/start', $request_data ); /** * Handle Query ID with dash * This query is located inside a component instance (not root) * hedzyv-flzcwg, hedzyv-hdcwtt * - hedzyv is the query element ID that holds the structure * - flzwg, hdcwtt is the element ID outside the component (unique), holds the actual properties * * @since 1.12.2 */ $data = []; if ( strpos( $query_element_id, '-' ) !== false ) { // The query is located in a component instance $part = explode( '-', $query_element_id ); $query_instance_id = ''; $element_id = ''; if ( count( $part ) === 2 ) { // The query element actual ID is the first part if ( ! empty( $part[0] ) ) { $query_instance_id = (string) $part[0]; } // Element Instance ID is the second part if ( ! empty( $part[1] ) ) { $element_id = (string) $part[1]; } } if ( empty( $query_instance_id ) || empty( $element_id ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Query element in component not found', ] ); } // Get the element data (data for flzwg), this will contains the cid (physical element in bricks data) $element_data = Helpers::get_element_data( $post_id, $element_id ); if ( empty( $element_data['element'] ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Element not found: ' . $element_id, ] ); } // Ensure element has cid if ( empty( $element_data['element']['cid'] ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Element is not a proper component: ' . $element_id, ] ); } $slot_query_data = self::get_slot_query_data( $query_instance_id, $element_data ); if ( $slot_query_data ) { $data = $slot_query_data; } else { // STEP: Get the component instance data (data for hedzyv) filled with properties $component_data_elements = Helpers::get_component_instance( $element_data['element'], 'elements' ); // STEP: Add parentComponent and instanceId for each element foreach ( $component_data_elements as $key => $component_data_element ) { $component_data_elements[ $key ]['parentComponent'] = $element_data['element']['cid']; $component_data_elements[ $key ]['instanceId'] = $element_id; $component_data_elements[ $key ]['ajaxLocalId'] = $element_id . '-' . $component_data_element['id']; // component children become local element when running generate_css_from_elements (#86c4957mc) } $component_data_elements = self::add_component_slot_context_elements( $component_data_elements, $element_data ); // Find the query element via query_instance_id from the component data $query_element = array_values( array_filter( $component_data_elements, function( $element ) use ( $query_instance_id ) { return (string) $element['id'] === $query_instance_id; } ) ); // Get the first element if it exists $query_element = ! empty( $query_element ) ? $query_element[0] : null; if ( empty( $query_element ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Query element not found: ' . $query_instance_id, ] ); } // Now build the data $data = [ 'element' => $query_element, 'elements' => $component_data_elements, 'source_id' => 'component', ]; } // Set query element id $query_element_id = $query_instance_id; } else { // Normal query element ID $data = Helpers::get_element_data( $post_id, $query_element_id ); } if ( empty( $data['elements'] ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Template data not found', ] ); } // STEP: Build the flat list index $indexed_elements = []; foreach ( $data['elements'] as $element ) { $indexed_elements[ $element['id'] ] = $element; } if ( ! array_key_exists( $query_element_id, $indexed_elements ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Element not found', ] ); } // Set current query element ID in the Api request, use in base.php and fore render the element (@since 2.1) self::$query_element_id = $query_element_id; // STEP: Set the query element pagination $query_element = $indexed_elements[ $query_element_id ]; // STEP: Replace query element with component data (@since 1.12.2) if ( isset( $query_element['cid'] ) ) { // Replace query element with component data $query_element = self::replace_query_element_with_component_data( $query_element, $query_element_id ); // Update indexed elements foreach ( $query_element['elements'] as $element ) { $indexed_elements[ $element['id'] ] = $element; } } // STEP: Get global query settings (@since 2.1) if ( isset( $query_element['settings']['query'] ) ) { $query_element['settings']['query'] = Helpers::maybe_get_global_query_settings( $query_element['settings']['query'] ?? [] ); } /** * STEP: Use hook to merge query_vars from the request instead of '_merge_vars' (@pre 1.9.5) * * Reason: _merge_vars not in use * - not reliable as it is using wp_parse_args(), only merge if the key is not set * - logic only occurs in post query, term and user not supported * * @since 1.9.5 */ $object_type = $query_element['settings']['query']['objectType'] ?? 'post'; if ( in_array( $object_type, [ 'term', 'user', 'array' ] ) ) { // Don't use request's offset, Term, User and Array query offset should be calculated inside Query::prepare_query_vars_from_settings() unset( $query_vars['offset'] ); } // NOTE: $ori_query_element_id could be a query element ID with dash (query inside a component instance). This type of query is supported in AJAX pagination only. Not Query filters. Must use this ID in hooks or we will get the wrong query. /** * Set the page number * * Needed for term query to calculate pagination correctly. * * @since 1.12.2 */ add_filter( 'bricks/query/prepare_query_vars_from_settings', function( $settings, $element_id ) use ( $page, $ori_query_element_id, $object_type ) { if ( $element_id !== $ori_query_element_id || ( $object_type !== 'term' && $object_type !== 'api' ) ) { return $settings; } $settings['query']['paged'] = $page; return $settings; }, 999, 2 ); add_filter( "bricks/{$object_type}s/query_vars", function( $vars, $settings, $element_id ) use ( $ori_query_element_id, $query_vars, $object_type, $page ) { if ( $element_id !== $ori_query_element_id ) { return $vars; } // STEP: Restore original query vars from frontend for dynamic parsed data (#86c4mgz6q; @since 2.0.1) $vars = Query::restore_original_query_vars_from_frontend( $query_vars, $vars, $object_type ); // Set the page number which comes from the request $query_vars['paged'] = $page; // Merge the query vars $merged_query_vars = Query::merge_query_vars( $vars, $query_vars ); return $merged_query_vars; }, 10, 3 ); if ( $search_template_id ) { $has_custom_criteria = Search::search_template_has_custom_criteria( $search_template_id ); if ( $has_custom_criteria ) { // Apply search criteria from the active search template add_filter( 'bricks/posts/query_vars', function( $query_vars, $settings, $element_id ) use ( $main_query_id, $search_template_id ) { if ( $element_id !== $main_query_id || empty( $query_vars['s'] ) ) { return $query_vars; } $search_term = $query_vars['s']; $post_ids = Search::get_search_template_criteria_post_ids( $search_template_id, $search_term ); // Remove default search query unset( $query_vars['s'] ); if ( ! empty( $post_ids ) ) { $query_vars['post__in'] = $post_ids; // Set orderby to post__in to preserve the order if weight score is used. If query filter sort is applied, skip this if ( Search::use_weight_score( $search_template_id ) && ! empty( $post_ids ) && ! isset( $query_vars['brx_sort_applied'] ) ) { $query_vars['orderby'] = 'post__in'; $query_vars['brx_orderby'] = 'weighted_relevance'; } } else { // No results found $query_vars['post__in'] = [ 0 ]; } return $query_vars; }, 999, 3 ); } } // Remove the parent if ( ! empty( $query_element['parent'] ) ) { $query_element['parent'] = 0; $query_element['_noRootClass'] = 1; } // STEP: Get the query loop elements, including slotted descendants for component instances (#86c7p02wq; @since 2.3.8) $loop_elements = self::get_query_loop_elements( $query_element, $indexed_elements ); // Set Theme Styles (for correct preview of query loop nodes) Theme_Styles::load_set_styles( $post_id ); // STEP: Generate the styles again to catch dynamic data changes (eg. background-image) $scroll_query_page_id = "scroll_{$query_element_id}_{$page}"; Assets::generate_css_from_elements( $loop_elements, $scroll_query_page_id ); $inline_css = ! empty( Assets::$inline_css[ $scroll_query_page_id ] ) ? Assets::$inline_css[ $scroll_query_page_id ] : ''; // STEP: Render the element after styles are generated as data-query-loop-index might be inserted through hook in Assets class (@since 1.7.2) $html = Frontend::render_data( $loop_elements ); // Add popup HTML plus styles (@since 1.7.1) $popups = Popups::$looping_popup_html; // STEP: Add dynamic data styles after render_data() to catch dynamic data changes (eg. background-image) (@since 1.8.2) $inline_css .= Assets::$inline_css_dynamic_data; $styles = ! empty( $inline_css ) ? "\n<style>/* INFINITE SCROLL CSS */\n{$inline_css}</style>\n" : ''; // STEP: Set the base URL for pagination or the pagination links will be using API endpoint if ( ! empty( $base_url ) ) { add_filter( 'bricks/paginate_links_args', function( $args ) use ( $base_url ) { $args['base'] = $base_url . '%_%'; return $args; } ); } $pagination = false; if ( $pagination_id ) { $element_data = Helpers::get_element_data( $post_id, $pagination_id ); if ( ! empty( $element_data['element'] ) ) { $pagination_element = $element_data['element'] ?? false; $pagination = Frontend::render_element( $pagination_element ); } else { // Maybe the pagination element is inside a component $pagination_component = array_filter( $data['elements'], function( $element ) use ( $pagination_id ) { return (string) $element['id'] === (string) $pagination_id; } ); $pagination_component = reset( $pagination_component ); if ( ! empty( $pagination_component ) ) { $pagination = Frontend::render_element( $pagination_component ); } } } // STEP: Query data, use original query ID $query_data = Helpers::get_query_object_from_history_or_init( $ori_query_element_id, $post_id ); // Remove unnecessary properties unset( $query_data->settings ); unset( $query_data->query_result ); unset( $query_data->loop_index ); unset( $query_data->loop_object ); unset( $query_data->is_looping ); unset( $query_data->fake_result ); if ( isset( $query_data->query_vars['queryEditor'] ) ) { unset( $query_data->query_vars['queryEditor'] ); } if ( isset( $query_data->query_vars['signature'] ) ) { unset( $query_data->query_vars['signature'] ); } return rest_ensure_response( [ 'html' => $html, 'styles' => $styles, 'popups' => $popups, 'pagination' => $pagination, 'updated_query' => $query_data, ] ); } /** * AJAX popup callback * * @since 1.9.4 */ public function render_popup_content( $request ) { $request_data = self::$request_data = self::sanitize_request_data( $request->get_json_params() ); $post_id = $request_data['postId'] ?? false; $popup_id = $request_data['popupId'] ?? false; $popup_loop_id = $request_data['popupLoopId'] ?? false; $popup_context_id = $request_data['popupContextId'] ?? false; $popup_context_type = $request_data['popupContextType'] ?? false; $query_element_id = $request_data['queryElementId'] ?? false; $language = isset( $request_data['lang'] ) ? sanitize_key( $request_data['lang'] ) : false; $main_query_id = isset( $request_data['mainQueryId'] ) ? sanitize_text_field( $request_data['mainQueryId'] ) : false; // Set current language (@since 2.0) if ( $language ) { Database::set_page_data_language( $language ); } // Set main query ID (@since 2.0) if ( $main_query_id ) { Database::$main_query_id = $main_query_id; } // Allow addtional actions for custom code (@since 2.0) do_action( 'bricks/render_popup_content/start', $request_data ); // Get Popup template settings and add classes to the popup content (@since 1.10.2) $popup_settings = Helpers::get_template_settings( $popup_id ); $is_woo_quick_view = isset( $popup_settings['popupIsWoo'] ) && Woocommerce::is_woocommerce_active(); // Handle WooCommerce Quick View in case no popupContextId has been defined (@since 1.10.2) if ( $is_woo_quick_view && ! $popup_context_id && $popup_loop_id ) { // Retrieve the context from popupLoopId $popup_loop_id_parts = explode( ':', $popup_loop_id ); if ( count( $popup_loop_id_parts ) === 4 ) { $popup_context_id = $popup_loop_id_parts[3]; $popup_loop_id = false; $query_element_id = false; } } // Set context in AJAX popup (post, term, user), $post_id might be zero on pages like 404, search, etc. if ( $post_id || $is_woo_quick_view || $popup_context_id ) { global $wp_query; global $post; $post = get_post( $post_id ); setup_postdata( $post ); /** * Set necessary global variables so we can use get_queried_object(), get_the_ID() etc. */ switch ( $popup_context_type ) { case 'post': if ( $popup_context_id ) { // Override the global post $post = get_post( $popup_context_id ); setup_postdata( $post ); } if ( ! $post || is_wp_error( $post ) ) { break; } $wp_query->queried_object = $post; $wp_query->queried_object_id = $post->ID; $wp_query->is_singular = true; $wp_query->post_type = $post->post_type; // Set is_single / is_page, otherwise comments_template wouldn't work (@since 1.10.2) if ( is_page( $post->ID ) ) { $wp_query->is_page = true; } else { $wp_query->is_single = true; } break; case 'term': $term = get_term( $popup_context_id ? $popup_context_id : $post_id ); if ( ! $term || is_wp_error( $term ) ) { break; } $wp_query->queried_object = $term; $wp_query->queried_object_id = $term->term_id; $wp_query->is_tax = true; break; case 'user': $user = get_user_by( 'id', $popup_context_id ? $popup_context_id : $post_id ); if ( ! $user || is_wp_error( $user ) ) { break; } $wp_query->queried_object = $user; $wp_query->queried_object_id = $user->ID; $wp_query->is_author = true; break; } } /** * Default: Current context (query element ID) * Re-run the query loop * Inaccurate, might be empty if inside a nested loop or repeater */ if ( $query_element_id ) { // Set page_data via filter add_filter( 'bricks/builder/data_post_id', function( $id ) use ( $post_id ) { return $post_id; } ); // Preview ID or post ID is very important in popup as it's a template, so we need to set separately Database::$page_data['preview_or_post_id'] = $post_id; $data = []; /** * Handle Query ID with dash * This query is located inside a component instance (not root) * hedzyv-flzcwg, hedzyv-hdcwtt * - hedzyv is the query element ID that holds the structure * - flzwg, hdcwtt is the element ID outside the component (unique), holds the actual properties * * @since 1.12.2 */ if ( strpos( $query_element_id, '-' ) !== false ) { // The query is located in a component instance $part = explode( '-', $query_element_id ); $query_instance_id = ''; $element_id = ''; if ( count( $part ) === 2 ) { // The query element actual ID is the first part if ( ! empty( $part[0] ) ) { $query_instance_id = (string) $part[0]; } // Element Instance ID is the second part if ( ! empty( $part[1] ) ) { $element_id = (string) $part[1]; } } if ( empty( $query_instance_id ) || empty( $element_id ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Query element in component not found', ] ); } // Get the element data (data for flzwg), this will contains the cid (physical element in bricks data) $element_data = Helpers::get_element_data( $post_id, $element_id ); if ( empty( $element_data['element'] ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Element not found: ' . $element_id, ] ); } // Ensure element has cid if ( empty( $element_data['element']['cid'] ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Element is not a proper component: ' . $element_id, ] ); } $slot_query_data = self::get_slot_query_data( $query_instance_id, $element_data ); if ( $slot_query_data ) { $data = $slot_query_data; } else { // STEP: Get the component instance data (data for hedzyv) filled with properties $component_data_elements = Helpers::get_component_instance( $element_data['element'], 'elements' ); // STEP: Add parentComponent and instanceId for each element foreach ( $component_data_elements as $key => $component_data_element ) { $component_data_elements[ $key ]['parentComponent'] = $element_data['element']['cid']; $component_data_elements[ $key ]['instanceId'] = $element_id; $component_data_elements[ $key ]['ajaxLocalId'] = $element_id . '-' . $component_data_element['id']; // component children become local element when running generate_css_from_elements (#86c4957mc) } $component_data_elements = self::add_component_slot_context_elements( $component_data_elements, $element_data ); // Find the query element via query_instance_id from the component data $query_element = array_values( array_filter( $component_data_elements, function( $element ) use ( $query_instance_id ) { return (string) $element['id'] === $query_instance_id; } ) ); // Get the first element if it exists $query_element = ! empty( $query_element ) ? $query_element[0] : null; if ( empty( $query_element ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Query element not found: ' . $query_instance_id, ] ); } // Now build the data $data = [ 'element' => $query_element, 'elements' => $component_data_elements, 'source_id' => 'component', ]; } // Set query element id $query_element_id = $query_instance_id; } else { // Normal query element ID // This popup inside a loop $data = Helpers::get_element_data( $post_id, $query_element_id ); } if ( empty( $data['elements'] ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'popups' => [], 'error' => esc_html__( 'Popup data not found', 'bricks' ), ] ); } // STEP: Build the flat list index $indexed_elements = []; foreach ( $data['elements'] as $element ) { $indexed_elements[ $element['id'] ] = $element; } if ( ! array_key_exists( $query_element_id, $indexed_elements ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'popups' => [], 'error' => esc_html__( 'Element not found', 'bricks' ), ] ); } // STEP: Set the query element pagination $query_element = $indexed_elements[ $query_element_id ]; // To solve looping popup without context issue (@since 1.12.2) if ( isset( $query_element['cid'] ) ) { // Replace query element with component data $query_element = self::replace_query_element_with_component_data( $query_element, $query_element_id ); // Update indexed elements foreach ( $query_element['elements'] as $element ) { $indexed_elements[ $element['id'] ] = $element; } // Must unset or the final query_vars is not using post_id (unknown reason) unset( $query_element['cid'] ); } // Get the target object ID from popupId string, separated by ':' if ( $popup_loop_id ) { $popup_id_parts = explode( ':', $popup_loop_id ); // $popup_id_parts at least 4 parts (@since 1.12) if ( count( $popup_id_parts ) >= 4 ) { $query_object_type = $popup_id_parts[2]; $query_object_id = $popup_id_parts[3]; $actual_query_id = $popup_id_parts[0]; $new_popup_loop_id = $popup_loop_id; switch ( $query_object_type ) { case 'post': $query_element['settings']['query']['p'] = $query_object_id; $new_popup_loop_id = "{$actual_query_id}:0:{$query_object_type}:{$query_object_id}"; break; case 'term': $query_element['settings']['query']['include'] = $query_object_id; $new_popup_loop_id = "{$actual_query_id}:0:{$query_object_type}:{$query_object_id}"; break; case 'user': $query_element['settings']['query']['include'] = $query_object_id; $new_popup_loop_id = "{$actual_query_id}:0:{$query_object_type}:{$query_object_id}"; break; default: case 'unknown': // Unable to detect query object type, this is inside repeater... query all ? // $query_element['settings']['query']['post_per_page'] = -1; // Return error and indicate not supported return rest_ensure_response( [ 'html' => '', 'styles' => '', 'popups' => [], 'error' => esc_html__( 'Query object type not supported', 'bricks' ), ] ); break; } } } // Remove the parent if ( ! empty( $query_element['parent'] ) ) { $query_element['parent'] = 0; $query_element['_noRootClass'] = 1; } // STEP: Get the query loop elements, including slotted descendants for component instances (#86c7p02wq; @since 2.3.8) $loop_elements = self::get_query_loop_elements( $query_element, $indexed_elements ); // Set Theme Styles (for correct preview of query loop nodes) Theme_Styles::load_set_styles( $post_id ); // STEP: Generate the styles again to catch dynamic data changes (eg. background-image) $looping_popup_id = "popup_{$query_element_id}_{$post_id}"; Assets::generate_css_from_elements( $loop_elements, $looping_popup_id ); $inline_css = ! empty( Assets::$inline_css[ $looping_popup_id ] ) ? Assets::$inline_css[ $looping_popup_id ] : ''; Frontend::render_data( $loop_elements, 'popup' ); $popups = Popups::$ajax_popup_contents; // Use $new_popup_loop_id to get popup content $popup_content = $popups[ $new_popup_loop_id ][ $popup_id ]['html'] ?? ''; // STEP: Add dynamic data styles after render_data() to catch dynamic data changes (eg. background-image) $inline_css .= Assets::$inline_css_dynamic_data; $styles = ! empty( $inline_css ) ? "\n<style>/*AJAX POPUP CSS */\n{$inline_css}</style>\n" : ''; } /** * Use user defined context (popupContextId) * More reliable than query_element_id way */ else { // Set page_data via filter add_filter( 'bricks/builder/data_post_id', function( $id ) use ( $post_id, $popup_context_id ) { // Use popup_context_id if not false return $popup_context_id ? $popup_context_id : $post_id; } ); // Preview or post id is very important in popup as it's a template, so we need to set separately Database::$page_data['preview_or_post_id'] = $popup_context_id ? $popup_context_id : $post_id; // This logic causing dynamic css not generated correctly (@since 1.12.2) // if ( $poup_is_looping ) { // Simulate Query::is_looping() as we skipped the query loop // add_filter( 'bricks/query/force_is_looping', '__return_true' ); // Simulate Query::get_loop_index() as we skipped the query loop // add_filter( // 'bricks/query/force_loop_index', // function( $index ) { // return 0; // } // ); // } // Get popup via popup ID $elements = Database::get_data( $popup_id ); if ( empty( $elements ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'popups' => [], 'error' => esc_html__( 'Popup data not found', 'bricks' ), ] ); } // Set active templates Database::set_active_templates( $post_id ); // Set Theme Styles (for correct preview of query loop nodes) Theme_Styles::load_set_styles( $post_id ); // STEP: Generate the styles again to catch dynamic data changes (eg. background-image) $popup_page_id = "popup_{$post_id}"; Assets::generate_css_from_elements( $elements, $popup_page_id ); $inline_css = Assets::$inline_css[ $popup_page_id ] ?? ''; $popup_content = Frontend::render_data( $elements, 'popup' ); $inline_css .= Assets::$inline_css_dynamic_data; $styles = ! empty( $inline_css ) ? "\n<style>/* AJAX POPUP CSS */\n{$inline_css}</style>\n" : ''; } $looping_popup_html = []; if ( ! empty( Popups::$looping_ajax_popup_ids ) ) { /** * In certain scenario, some popup templates inserted inside a query loop which is inside another AJAX popup template * Generate each looping AJAX popup html holder, we could use this to add into the DOM if it's not there yet */ foreach ( Popups::$looping_ajax_popup_ids as $looping_popup_id ) { $html = Popups::generate_popup_html( $looping_popup_id ); $looping_popup_html[ $looping_popup_id ] = $html; } } $response = [ 'html' => $popup_content, 'styles' => $styles, 'popups' => $looping_popup_html, ]; // Add Woo quick view classes so JS can insert into popup content node (@since 1.10.2) if ( $is_woo_quick_view ) { global $product; $response['contentClasses'] = (array) wc_get_product_class( '', $product ); } return rest_ensure_response( $response ); } /** * Ajax Popup permissions callback * * @since 1.9.4 */ public function render_popup_content_permissions_check( $request ) { $data = $request->get_json_params(); if ( empty( $data['popupId'] ) || empty( $data['nonce'] ) ) { return new \WP_Error( 'bricks_api_missing', __( 'Missing parameters' ), [ 'status' => 400 ] ); } $result = wp_verify_nonce( $data['nonce'], 'bricks-nonce' ); if ( $result === false ) { return new \WP_Error( 'rest_cookie_invalid_nonce', __( 'Bricks cookie check failed' ), [ 'status' => 403 ] ); } return true; } /** * Similar like render_query_page() but for AJAX query result * * For load more, AJAX pagination, infinite scroll, sort, filter, live search. * * @since 1.9.6 */ public function render_query_result( $request ) { $request_data = self::$request_data = self::sanitize_request_data( $request->get_json_params() ); $query_element_id = $request_data['queryElementId']; $post_id = $request_data['postId']; $filters = $request_data['filters'] ?? []; $selected_filters = $request_data['selectedFilters'] ?? []; $active_filters_tags = $request_data['afTags'] ?? []; $page_filters = $request_data['pageFilters'] ?? []; $base_url = $request_data['baseUrl'] ?? ''; $language = isset( $request_data['lang'] ) ? sanitize_key( $request_data['lang'] ) : false; $infinite_page = isset( $request_data['infinitePage'] ) ? sanitize_text_field( $request_data['infinitePage'] ) : 1; $original_query_vars = isset( $request_data['originalQueryVars'] ) ? json_decode( $request_data['originalQueryVars'], true ) : []; $main_query_id = isset( $request_data['mainQueryId'] ) ? sanitize_text_field( $request_data['mainQueryId'] ) : false; $search_template_id = isset( $request_data['activeSearchTemplate'] ) ? absint( $request_data['activeSearchTemplate'] ) : 0; // Set current language (@since 1.9.9) if ( $language ) { Database::set_page_data_language( $language ); } // Set main query ID (@since 2.0) if ( $main_query_id ) { Database::$main_query_id = $main_query_id; } // Set active search template ID to apply search criteria setting (@since 2.2) if ( $search_template_id ) { self::$active_templates['search'] = $search_template_id; } // Set post_id for use in prepare_query_vars_from_settings Database::$page_data['preview_or_post_id'] = $post_id; // Allow addtional actions for custom code. WPML (@since 1.12.2) do_action( 'bricks/render_query_result/start', $request_data ); $data = Helpers::get_element_data( $post_id, $query_element_id ); if ( empty( $data['elements'] ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Template data not found', ] ); } // STEP: Build the flat list index $indexed_elements = []; foreach ( $data['elements'] as $element ) { $indexed_elements[ $element['id'] ] = $element; } if ( ! array_key_exists( $query_element_id, $indexed_elements ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Element not found', ] ); } // Set current query element ID in the Api request, use in base.php and fore render the element (@since 2.1) self::$query_element_id = $query_element_id; // STEP: Set the query element pagination $query_element = $indexed_elements[ $query_element_id ]; // STEP: Replace query element with component data (@since 1.12.2) if ( isset( $query_element['cid'] ) ) { // Replace query element with component data $query_element = self::replace_query_element_with_component_data( $query_element, $query_element_id ); // Update indexed elements foreach ( $query_element['elements'] as $element ) { $indexed_elements[ $element['id'] ] = $element; } } $query_object_type = isset( $query_element['settings']['query']['objectType'] ) ? sanitize_text_field( $query_element['settings']['query']['objectType'] ) : 'post'; // Return error: Not a post, term or user query if ( ! in_array( $query_object_type, [ 'post', 'term', 'user' ] ) ) { return rest_ensure_response( [ 'html' => '', 'styles' => '', 'error' => 'Query object type not supported', ] ); } // STEP: Set page filters Query_Filters::set_page_filters( $page_filters ); // STEP: Set active filters Query_Filters::set_active_filters( $selected_filters, $post_id, $query_element_id ); // STEP: Set flag for query_vars (@since 1.12) Query_Filters::set_generating_type( $query_object_type ); // STEP: generate query vars from active filters $filter_query_vars = Query_Filters::generate_query_vars_from_active_filters( $query_element_id ); // STEP: Check if query merge is disabled $disable_query_merge = $query_element['settings']['query']['disable_query_merge'] ?? false; // STEP: Use infinite page number if ( $infinite_page > 1 ) { $filter_query_vars['paged'] = $infinite_page; } // Set the paged & number - This is needed for term query to calculate pagination correctly (@since 1.12) if ( $query_object_type === 'term' ) { if ( ( isset( $filter_query_vars['paged'] ) && $filter_query_vars['paged'] > 1 ) || ( isset( $filter_query_vars['number'] ) && $filter_query_vars['number'] > 0 ) ) { add_filter( 'bricks/query/prepare_query_vars_from_settings', function( $settings, $element_id ) use ( $filter_query_vars, $query_element_id, $query_object_type ) { if ( $element_id !== $query_element_id || $query_object_type !== 'term' ) { return $settings; } // Set paged value if ( isset( $filter_query_vars['paged'] ) ) { $settings['query']['paged'] = $filter_query_vars['paged']; } // Set number value if ( isset( $filter_query_vars['number'] ) ) { // Backup the user original number value if ( isset( $settings['query']['number'] ) ) { $settings['query']['brx_user_number'] = $settings['query']['number']; } // Set the new number value $settings['query']['number'] = $filter_query_vars['number']; } return $settings; }, 10, 2 ); } } // STEP: Merge the query vars via filter, so we can override WooCommerce query vars, queryEditor query vars, etc. add_filter( "bricks/{$query_object_type}s/query_vars", function( $vars, $settings, $element_id ) use ( $filter_query_vars, $query_element_id, $disable_query_merge, $original_query_vars, $query_object_type ) { if ( $element_id !== $query_element_id ) { return $vars; } // Save a copy for restoring (@since 2.3.2) $restoring_query_vars = $original_query_vars; // Unset "s" if met should_reconcile_search_query_vars #86c92m5v4, #86c86uf21 (@since 2.3.2) if ( Query_Filters::should_reconcile_search_query_vars( $query_element_id, $query_object_type, $filter_query_vars ) ) { unset( $restoring_query_vars['s'] ); } // STEP: Restore original query vars from the frontend (dynamic data parsed) (@since 1.12) $vars = Query::restore_original_query_vars_from_frontend( $restoring_query_vars, $vars, $query_object_type ); // STEP: Original query vars should include page filters (if it's not disabled) (@since 1.11) if ( ! $disable_query_merge && Query_Filters::should_apply_page_filters( $vars ) ) { $vars = Query::merge_query_vars( $vars, Query_Filters::generate_query_vars_from_page_filters() ); } // STEP: User query 'user_role' parameter, validate parameter if user_role defined in the settingssettings/via hook. (#86c7hjhky @since 2.2) if ( $query_object_type === 'user' && isset( $vars['role__in'] ) && isset( $filter_query_vars['role__in'] ) ) { $url_value = $filter_query_vars['role__in']; $setting_value = $vars['role__in']; // url_value might be string or array $url_value_array = is_array( $url_value ) ? $url_value : [ $url_value ]; $setting_value_array = is_array( $setting_value ) ? $setting_value : [ $setting_value ]; // Only keep the role__in value that exists in settings $filtered_roles = array_intersect( $url_value_array, $setting_value_array ); if ( ! empty( $filtered_roles ) ) { $filter_query_vars['role__in'] = array_values( $filtered_roles ); } else { // No matching role, set to impossible value to avoid returning all users $filter_query_vars['role__in'] = [ 0 ]; } } // STEP: Save the query vars before merge only once (@since 1.11.1) if ( ! isset( Query_Filters::$query_vars_before_merge[ $query_element_id ] ) ) { Query_Filters::$query_vars_before_merge[ $query_element_id ] = $vars; // For term and user query, must save the user original number value or it will be overwritten by url parameter value after page reload if ( in_array( $query_object_type, [ 'term', 'user' ], true ) && isset( $vars['brx_user_number'] ) ) { Query_Filters::$query_vars_before_merge[ $query_element_id ]['number'] = $vars['brx_user_number']; // Cleanup unset( $vars['brx_user_number'] ); unset( Query_Filters::$query_vars_before_merge[ $query_element_id ]['brx_user_number'] ); } } // STEP: Merge the query vars from filters $final = Query::merge_query_vars( $vars, $filter_query_vars ); return $final; }, 999, // As long as using Query Filters, the filter's query var should be the last (@since 1.11.1) 3 ); // STEP: Reset flasg (@since 1.12) Query_Filters::reset_generating_type(); // Remove the parent if ( ! empty( $query_element['parent'] ) ) { $query_element['parent'] = 0; $query_element['_noRootClass'] = 1; } // STEP: Get the query loop elements, including slotted descendants for component instances (#86c7p02wq; @since 2.3.8) $loop_elements = self::get_query_loop_elements( $query_element, $indexed_elements ); // Set Theme Styles (for correct preview of query loop nodes) Theme_Styles::load_set_styles( $post_id ); // STEP: Generate the styles again to catch dynamic data changes (eg. background-image) $query_identifier = "ajax_query_{$query_element_id}"; Assets::generate_css_from_elements( $loop_elements, $query_identifier ); $inline_css = ! empty( Assets::$inline_css[ $query_identifier ] ) ? Assets::$inline_css[ $query_identifier ] : ''; // STEP: Render the element after styles are generated as data-query-loop-index might be inserted through hook in Assets class $html = Frontend::render_data( $loop_elements ); // Add popup HTML plus styles $popups = Popups::$looping_popup_html; // STEP: Add dynamic data styles after render_data() to catch dynamic data changes (eg. background-image) $inline_css .= Assets::$inline_css_dynamic_data; $styles = ! empty( $inline_css ) ? "\n<style>/* AJAX QUERY RESULT CSS */\n{$inline_css}</style>\n" : ''; // STEP: Set the base URL for pagination or the pagination links will be using API endpoint if ( ! empty( $base_url ) ) { add_filter( 'bricks/paginate_links_args', function( $args ) use ( $base_url ) { $args['base'] = $base_url . '%_%'; return $args; } ); } // STEP: Get updated filters HTML $updated_filters = Query_Filters::get_updated_filters( $filters, $post_id ); // STEP: Query data $query_data = Helpers::get_query_object_from_history_or_init( $query_element_id, $post_id ); // Remove unnecessary properties unset( $query_data->settings ); unset( $query_data->query_result ); unset( $query_data->loop_index ); unset( $query_data->loop_object ); unset( $query_data->is_looping ); unset( $query_data->fake_result ); if ( isset( $query_data->query_vars['queryEditor'] ) ) { unset( $query_data->query_vars['queryEditor'] ); } if ( isset( $query_data->query_vars['signature'] ) ) { unset( $query_data->query_vars['signature'] ); } // Get the active filters count via Dynamic Data (@since 2.0) $parsed_af_tags = []; if ( is_array( $active_filters_tags ) && ! empty( $active_filters_tags ) ) { foreach ( $active_filters_tags as $tag ) { if ( ! is_string( $tag ) ) { continue; } $tag = sanitize_text_field( trim( $tag ) ); // Only parse dynamic data tags starting with 'active_filters_count' if ( strpos( $tag, 'active_filters_count' ) !== 0 ) { continue; } $parsed_af_tags[ $tag ] = Integrations\Dynamic_Data\Providers::render_tag( "{$tag}", $post_id ); } } return rest_ensure_response( [ 'html' => $html, 'styles' => $styles, 'popups' => $popups, 'updated_filters' => $updated_filters, 'updated_query' => $query_data, 'parsed_af_tags' => $parsed_af_tags, // 'page_filters' => Query_Filters::$page_filters, // 'filter_object_ids' => Query_Filters::$filter_object_ids, // 'active_filters' => Query_Filters::$active_filters, ] ); } /** * Query loop: Query result permissions callback * * @since 1.9.6 */ public function render_query_result_permissions_check( $request ) { $data = $request->get_json_params(); if ( empty( $data['queryElementId'] ) || empty( $data['nonce'] ) ) { return new \WP_Error( 'bricks_api_missing', __( 'Missing parameters' ), [ 'status' => 400 ] ); } $result = wp_verify_nonce( $data['nonce'], 'bricks-nonce' ); if ( $result === false ) { return new \WP_Error( 'rest_cookie_invalid_nonce', __( 'Bricks cookie check failed' ), [ 'status' => 403 ] ); } return true; } /** * Get global classes categorized by their usage across the site * * @since 1.12 */ public function get_global_classes_site_usage() { return rest_ensure_response( [ 'data' => Helpers::scan_global_classes_site_usage() ] ); } /** * Permission check for get_global_classes_site_usage endpoint */ public function get_global_classes_site_usage_permissions_check( $request ) { $nonce = $request->get_header( 'X-Bricks-Nonce' ); $result = wp_verify_nonce( $nonce, 'bricks-nonce-builder' ); if ( $result === false ) { return new \WP_Error( 'rest_cookie_invalid_nonce', __( 'Bricks cookie check failed' ), [ 'status' => 403 ] ); } // Return: Current user does not have full access if ( ! Builder_Permissions::user_has_permission( 'access_class_manager' ) ) { return new \WP_Error( 'rest_current_user_does_not_have_full_access', __( 'Current user does not have access to get global classes site usage' ), [ 'status' => 403 ] ); } return true; } /** * Add local component slot context to AJAX component render data. * * Component query AJAX renders use component definition elements. Slot elements still * need the page-local component instance and its slot payloads in Frontend::$elements. * * @param array $component_elements Component definition elements. * @param array $component_instance_data Component instance data from Helpers::get_element_data(). * * @return array * * @since 2.3.8 */ private static function add_component_slot_context_elements( $component_elements, $component_instance_data ) { if ( ! is_array( $component_elements ) || empty( $component_instance_data ) || ! is_array( $component_instance_data ) ) { return []; } $component_instance_element = $component_instance_data['element'] ?? []; if ( ! empty( $component_instance_element['id'] ) ) { if ( empty( $component_instance_element['parent'] ) ) { $component_instance_element['parent'] = '__bricks_ajax_context'; } $component_elements[] = $component_instance_element; } $slot_child_elements = Helpers::get_component_slot_child_elements( $component_instance_element, $component_instance_data['elements'] ?? [] ); foreach ( $slot_child_elements as $slot_child_element ) { $component_elements[] = $slot_child_element; } return $component_elements; } /** * Get query data for a local query loop inserted into a component slot. * * Dashed query IDs can represent a query inside a component definition or a page-local * query inserted into an instance slot. The latter is stored in the page element tree, so * it must be resolved from slotChildren before falling back to component definition data. * (#86c7p02wq; @since 2.3.8) * * @param string $query_element_id Query loop element ID. * @param array $component_instance_data Component instance element data from Helpers::get_element_data(). * * @return array|false * * @since 2.3.8 */ private static function get_slot_query_data( $query_element_id, $component_instance_data ) { if ( empty( $query_element_id ) || empty( $component_instance_data ) || ! is_array( $component_instance_data ) ) { return false; } $component_instance = $component_instance_data['element'] ?? []; $source_elements = $component_instance_data['elements'] ?? []; if ( empty( $component_instance['slotChildren'] ) || empty( $source_elements ) || ! is_array( $source_elements ) ) { return false; } $indexed_elements = []; foreach ( $source_elements as $source_element ) { if ( empty( $source_element['id'] ) ) { continue; } $indexed_elements[ (string) $source_element['id'] ] = $source_element; } foreach ( Helpers::get_component_slot_child_elements( $component_instance, $source_elements ) as $slot_child_element ) { if ( empty( $slot_child_element['id'] ) ) { continue; } $slot_child_id = (string) $slot_child_element['id']; $slot_child_key = (string) ( $slot_child_element['slotElementKey'] ?? $slot_child_id ); // Keep both lookups: contextual slot keys avoid collisions, raw IDs support the existing query ID contract. $indexed_elements[ $slot_child_key ] = isset( $indexed_elements[ $slot_child_key ] ) ? array_merge( $indexed_elements[ $slot_child_key ], $slot_child_element ) : $slot_child_element; $indexed_elements[ $slot_child_id ] = isset( $indexed_elements[ $slot_child_id ] ) ? array_merge( $indexed_elements[ $slot_child_id ], $slot_child_element ) : $slot_child_element; } if ( empty( $indexed_elements[ (string) $query_element_id ] ) ) { return false; } return [ 'element' => $indexed_elements[ (string) $query_element_id ], 'elements' => array_values( $indexed_elements ), 'source_id' => $component_instance_data['source_id'] ?? 0, ]; } /** * Get query loop elements including component slot children. * * AJAX pagination renders from a partial element list, so component instance slot descendants * must be added explicitly; they are stored as local page children, not component children. * (#86c7p02wq; @since 2.3.8) * * @param array $query_element Query loop element. * @param array $indexed_elements Elements indexed by ID. * * @return array * * @since 2.3.8 */ private static function get_query_loop_elements( $query_element, &$indexed_elements ) { $loop_elements = [ $query_element ]; $loop_element_ids = []; $query_element_id = isset( $query_element['id'] ) ? (string) $query_element['id'] : ''; if ( $query_element_id ) { $loop_element_ids[ $query_element_id ] = true; } $children = ! empty( $query_element['children'] ) && is_array( $query_element['children'] ) ? $query_element['children'] : []; while ( ! empty( $children ) ) { $child_id = (string) array_shift( $children ); if ( array_key_exists( $child_id, $indexed_elements ) ) { $loop_elements[] = $indexed_elements[ $child_id ]; $loop_element_ids[ $child_id ] = true; if ( ! empty( $indexed_elements[ $child_id ]['children'] ) && is_array( $indexed_elements[ $child_id ]['children'] ) ) { $children = array_merge( $children, $indexed_elements[ $child_id ]['children'] ); } } } $component_instance_id = ! empty( $query_element['instanceId'] ) ? (string) $query_element['instanceId'] : ''; // Slot elements need their local component instance in Frontend::$elements to resolve slotChildren. if ( $component_instance_id && isset( $indexed_elements[ $component_instance_id ] ) && ! isset( $loop_element_ids[ $component_instance_id ] ) ) { $component_instance_element = $indexed_elements[ $component_instance_id ]; if ( empty( $component_instance_element['parent'] ) ) { $component_instance_element['parent'] = '__bricks_ajax_context'; } $loop_elements[] = $component_instance_element; $loop_element_ids[ $component_instance_id ] = true; } $index = 0; while ( isset( $loop_elements[ $index ] ) ) { $slot_child_elements = Helpers::get_component_slot_child_elements( $loop_elements[ $index ], $indexed_elements ); foreach ( $slot_child_elements as $slot_child_element ) { if ( empty( $slot_child_element['id'] ) ) { continue; } $slot_child_id = (string) $slot_child_element['id']; $slot_child_key = (string) ( $slot_child_element['slotElementKey'] ?? $slot_child_id ); // Use the contextual key for de-duplication so repeated slot source IDs stay renderable. $indexed_elements[ $slot_child_key ] = $slot_child_element; if ( ! isset( $indexed_elements[ $slot_child_id ] ) ) { $indexed_elements[ $slot_child_id ] = $slot_child_element; } if ( isset( $loop_element_ids[ $slot_child_key ] ) ) { continue; } $loop_elements[] = $slot_child_element; $loop_element_ids[ $slot_child_key ] = true; } $index++; } return $loop_elements; } public static function replace_query_element_with_component_data( $query_element, $query_element_id ) { $component_settings = Helpers::get_component_instance( $query_element, 'settings' ); // Update settings $query_element['settings'] = $component_settings ?? $query_element['settings']; $component_chidren = Helpers::get_component_instance( $query_element, 'children' ); // Update children $query_element['children'] = $component_chidren ?? $query_element['children']; $component_elements = Helpers::get_component_instance( $query_element, 'elements' ); // Update elements $query_element['elements'] = $component_elements ?? $query_element['elements']; // Replace all cid with query_element_id $query_element_elements_string = json_encode( $query_element['elements'] ); $query_element_elements_string = str_replace( $query_element['cid'], $query_element_id, $query_element_elements_string ); $query_element['elements'] = json_decode( $query_element_elements_string, true ); return $query_element; } }
修改文件时间
将文件时间修改为当前时间的前一年
删除文件