id}_scripts", [$this, 'register_default_scripts']); add_action("wu_{$this->id}_scripts", [$this, 'register_scripts']); add_action('wp', [$this, 'maybe_setup']); add_action('admin_head', [$this, 'setup_for_admin'], 100); add_filter('pre_render_block', [$this, 'setup_for_block_editor'], 100, 2); add_action('wu_element_preview', [$this, 'setup_preview']); // Init should be the correct time to call this to avoid the deprecated notice from I18N. // But it doesn't work for some reason, fix later. // add_action('init', function () { do_action('wu_element_loaded', $this); // } ); if ($this->public) { self::register_public_element($this); } } /** * Register a public element to further use. * * @since 2.0.24 * @param mixed $element The element instance to be registered. * @return void */ public static function register_public_element($element): void { static::$public_elements[] = $element; } /** * Retrieves the public registered elements. * * @since 2.0.24 * @return array */ public static function get_public_elements() { return static::$public_elements; } /** * Sets blocks up for the block editor. * * @since 2.0.0 * * @param null $short_circuit The value passed. * @param array $block The parsed block data. * @return null */ public function setup_for_block_editor($short_circuit, $block) { $should_load = false; if ($block['blockName'] === $this->get_id()) { $should_load = true; } /** * We might need to add additional blocks later. * * @since 2.0.0 * @return array */ $blocks_to_check = apply_filters( 'wu_element_block_types_to_check', [ 'core/shortcode', 'core/paragraph', ] ); if (in_array($block['blockName'], $blocks_to_check, true)) { if ($this->contains_current_element($block['innerHTML'])) { $should_load = true; } } if ($should_load) { if ($this->is_preview()) { $this->setup_preview(); } else { $this->setup(); } } return $short_circuit; } /** * Search for an element id on the list of metaboxes. * * Builds a cached list of elements on the first run. * Then uses the cache to run a simple in_array check. * * @since 2.0.0 * * @param string $element_id The element ID. * @return bool */ protected static function search_in_metaboxes($element_id) { global $wp_meta_boxes, $pagenow; /* * Bail if things don't look normal or in the right context. */ if ( ! function_exists('get_current_screen')) { return; } $screen = get_current_screen(); /* * First, check on cache, to avoid recalculating it time and time again. */ if (is_array(self::$metabox_cache)) { return in_array($element_id, self::$metabox_cache, true); } $contains_metaboxes = wu_get_isset($wp_meta_boxes, $screen->id) || wu_get_isset($wp_meta_boxes, $pagenow); $elements_to_cache = []; $found = false; if (is_array($wp_meta_boxes) && $contains_metaboxes && is_array($wp_meta_boxes[ $screen->id ])) { foreach ($wp_meta_boxes[ $screen->id ] as $position => $priorities) { foreach ($priorities as $priority => $metaboxes) { foreach ($metaboxes as $metabox_id => $metabox) { $elements_to_cache[] = $metabox_id; if ($metabox_id === $element_id) { $found = true; } } } } /** * Set a local cache so we don't have to loop it all over again. */ self::$metabox_cache = $elements_to_cache; } return $found; } /** * Setup element on admin pages. * * @since 2.0.0 * @return void */ public function setup_for_admin(): void { if ($this->loaded === true) { return; } $element_id = "wp-ultimo-{$this->id}-element"; if (self::search_in_metaboxes($element_id)) { $this->loaded = true; $this->setup(); } } /** * Maybe run setup, when the shortcode or block is found. * * @todo check if this is working only when necessary. * @since 2.0.0 * @return void */ public function maybe_setup(): void { global $post; if (is_admin() || empty($post)) { return; } if ($this->contains_current_element($post->post_content, $post)) { if ($this->is_preview()) { $this->setup_preview(); } else { $this->setup(); } } } /** * Runs early on the request lifecycle as soon as we detect the shortcode is present. * * @since 2.0.0 * @return void */ public function setup() {} /** * Allows the setup in the context of previews. * * @since 2.0.0 * @return void */ public function setup_preview() {} /** * Checks content to see if the current element is present. * * This check uses different methods, covering classic shortcodes, * blocks. It also adds a generic filter so developers can * add additional tests for different builders and so on. * * @since 2.0.0 * * @param string $content The content that might contain the element. * @param null|\WP_Post $post The WP Post, if it exists. * @return bool */ protected function contains_current_element($content, $post = null) { /** * If parameters where pre-loaded, * we can skip the entire check and return true. */ if (is_array($this->pre_loaded_attributes)) { return true; } /* * First, check for default shortcodes * saved as regular post content. */ $shortcode = $this->get_shortcode_id(); if (has_shortcode($content, $shortcode)) { $this->pre_loaded_attributes = $this->maybe_extract_arguments($content, 'shortcode'); $this->actually_loaded = true; return true; } /* * Handle the Block Editor * and Gutenberg. */ $block = $this->get_id(); if (has_block($block, $content)) { $this->pre_loaded_attributes = $this->maybe_extract_arguments($content, 'block'); $this->actually_loaded = true; return true; } /* * Runs generic version so plugins can extend it. */ $this->pre_loaded_attributes = $this->maybe_extract_arguments($content, 'other'); $contains_element = false; /** * Last option is to check for the post force setting. */ if ($post && get_post_meta($post->ID, '_wu_force_elements_loading', true)) { $contains_element = true; } /** * Allow developers to change the results of the initial search. * * This is useful for third-party builders and such. * * @since 2.0.0 * @param bool $contains_elements If the element is contained on the content. * @param string $content The content being examined. * @param self The current element. */ return apply_filters('wu_contains_element', $contains_element, $content, $this, $post); } /** * Tries to extract element arguments depending on the element type. * * @since 2.0.0 * * @param string $content The content to parse. * @param string $type The element type. Can be one of shortcode, block, and other. * @return false|array */ protected function maybe_extract_arguments($content, $type = 'shortcode') { if ('shortcode' === $type) { /** * Tries to parse the shortcode out of the content * passed using the WordPress shortcode regex. */ $shortcode_regex = get_shortcode_regex([$this->get_shortcode_id()]); preg_match_all('/' . $shortcode_regex . '/', $content, $matches, PREG_SET_ORDER); return ! empty($matches) ? shortcode_parse_atts($matches[0][3]) : false; } elseif ('block' === $type) { /** * Next, try to parse attrs from blocks * by parsing them out and finding the correct one. */ $block_content = parse_blocks($content); foreach ($block_content as $block) { if ($block['blockName'] === $this->get_id()) { return $block['attrs']; } } return false; } /** * Adds generic filter to allow developers * to extend this parser to deal with additional * builders or plugins. * * @since 2.0.0 * @return false|array */ return apply_filters('wu_element_maybe_extract_arguments', false, $content, $type, $this); } /** * Adds custom CSS to the signup screen. * * @since 2.0.0 * @return void */ public function enqueue_element_scripts(): void { global $post; if ( ! is_a($post, '\WP_Post')) { return; } $should_enqueue_scripts = apply_filters('wu_element_should_enqueue_scripts', false, $post, $this->get_shortcode_id()); if ($should_enqueue_scripts || $this->contains_current_element($post->post_content, $post)) { /** * Triggers the enqueue scripts hook. * * This is used by the element to hook its * register_scripts method. * * @since 2.0.0 */ do_action("wu_{$this->id}_scripts", $post, $this); } } /** * Tries to parse the shortcode content on page load. * * This allow us to have access to parameters before the shortcode * gets actually parsed by the post content functions such as * the_content(). It is useful if you need to access that * date way earlier in the page lifecycle. * * @since 2.0.0 * * @param string $name The parameter name. * @param mixed $default The default value. * @return mixed */ public function get_pre_loaded_attribute($name, $default = false) { if ($this->pre_loaded_attributes === false || ! is_array($this->pre_loaded_attributes)) { return false; } return wu_get_isset($this->pre_loaded_attributes, $name, $default); } /** * Registers the shortcode. * * @since 2.0.0 * @return void */ public function register_shortcode(): void { if (wu_get_current_site()->get_type() === Site_Type::CUSTOMER_OWNED && is_admin() === false) { return; } add_shortcode($this->get_shortcode_id(), [$this, 'display']); } /** * Registers the forms. * * @since 2.0.0 * @return void */ public function register_form(): void { /* * Add Generator Forms */ wu_register_form( "shortcode_{$this->id}", [ 'render' => [$this, 'render_generator_modal'], 'handler' => '__return_empty_string', 'capability' => 'manage_network', ] ); /* * Add Customize Forms */ wu_register_form( "customize_{$this->id}", [ 'render' => [$this, 'render_customize_modal'], 'handler' => [$this, 'handle_customize_modal'], 'capability' => 'manage_network', ] ); } /** * Adds the modal to copy the shortcode for this particular element. * * @since 2.0.0 * @return void */ public function render_generator_modal(): void { $fields = $this->fields(); $defaults = $this->defaults(); $state = []; foreach ($fields as $field_slug => &$field) { if ($field['type'] === 'header' || $field['type'] === 'note') { unset($fields[ $field_slug ]); continue; } /* * Additional State. * * We need to keep track of the state * specially when we're dealing with * complex fields, such as group. */ $additional_state = []; if ($field['type'] === 'group') { foreach ($field['fields'] as $sub_field_slug => &$sub_field) { $sub_field['html_attr'] = [ 'v-model.lazy' => "attributes.{$sub_field_slug}", ]; $additional_state[ $sub_field_slug ] = wu_request($sub_field_slug, wu_get_isset($defaults, $sub_field_slug)); } continue; } /* * Set v-model */ $field['html_attr'] = [ 'v-model.lazy' => "attributes.{$field_slug}", ]; $required = wu_get_isset($field, 'required'); if (wu_get_isset($field, 'required')) { $shows = []; foreach ($required as $key => $value) { $value = is_string($value) ? "\"$value\"" : $value; $shows[] = "attributes.{$key} == $value"; } $field['wrapper_html_attr'] = [ 'v-show' => implode(' && ', $shows), ]; $state[ $field_slug . '_shortcode_requires' ] = $required; } $state[ $field_slug ] = wu_request($field_slug, wu_get_isset($defaults, $field_slug)); } $fields['shortcode_result'] = [ 'type' => 'note', 'wrapper_classes' => 'sm:wu-block', 'desc' => '