is_elementor_active() ) { return; } // Register the Schema AI widget category. add_action( 'elementor/elements/categories_registered', array( $this, 'register_widget_category' ) ); // Register Schema AI widgets. add_action( 'elementor/widgets/register', array( $this, 'register_widgets' ) ); // Provide Elementor content to Schema AI content analysis. add_filter( 'schema_ai_post_content', array( $this, 'maybe_use_elementor_content' ), 10, 2 ); } /** * Register the 'ai-schema-gen' widget category in Elementor. * * @param \Elementor\Elements_Manager $elements_manager The Elementor elements manager. */ public function register_widget_category( $elements_manager ) { $elements_manager->add_category( 'ai-schema-gen', array( 'title' => esc_html__( 'AI Schema Gen', 'ai-schema-gen' ), 'icon' => 'eicon-code', ) ); } /** * Register Schema AI widgets with Elementor. * * Registers a lightweight schema status widget that shows whether * schema markup has been applied to the current page. * * @param \Elementor\Widgets_Manager $widgets_manager The Elementor widgets manager. */ public function register_widgets( $widgets_manager ) { // Only register if the base widget class is available. if ( ! class_exists( '\Elementor\Widget_Base' ) ) { return; } // Load the widget class file. $widget_file = SCHEMA_AI_PLUGIN_DIR . 'includes/integrations/widgets/class-elementor-schema-widget.php'; if ( file_exists( $widget_file ) ) { require_once $widget_file; if ( class_exists( 'Schema_AI_Elementor_Schema_Widget' ) ) { $widgets_manager->register( new Schema_AI_Elementor_Schema_Widget() ); } } } /** * Replace post content with Elementor-extracted content when appropriate. * * Hooked to 'schema_ai_post_content' filter. If the post uses Elementor, * extract its widget text content instead of the raw post_content which * contains Elementor shortcodes and JSON. * * @param string $content The current post content. * @param int $post_id The WordPress post ID. * @return string The content string (possibly replaced with Elementor text). */ public function maybe_use_elementor_content( $content, $post_id ) { // Check if this post was built with Elementor. $is_elementor = get_post_meta( $post_id, '_elementor_edit_mode', true ); if ( empty( $is_elementor ) ) { return $content; } $elementor_content = $this->extract_elementor_content( $post_id ); if ( ! empty( $elementor_content ) ) { return $elementor_content; } return $content; } /** * Extract plain-text content from Elementor page-builder data. * * Reads the serialised Elementor JSON from post meta, parses it, and * recursively walks through all elements extracting text from headings, * text-editor widgets, and other content-bearing widgets. * * @param int $post_id The WordPress post ID. * @return string Plain-text content extracted from Elementor data. */ public function extract_elementor_content( $post_id ) { $elementor_data = get_post_meta( $post_id, '_elementor_data', true ); if ( empty( $elementor_data ) ) { return ''; } // Elementor stores data as a JSON string. if ( is_string( $elementor_data ) ) { $elementor_data = json_decode( $elementor_data, true ); } if ( ! is_array( $elementor_data ) ) { return ''; } $text_parts = $this->parse_elementor_elements( $elementor_data ); if ( empty( $text_parts ) ) { return ''; } // Join all extracted text pieces with newlines and clean up. $content = implode( "\n\n", $text_parts ); $content = wp_strip_all_tags( $content ); $content = preg_replace( '/\s+/', ' ', $content ); $content = trim( $content ); return $content; } /** * Recursively parse Elementor elements to extract text content. * * Walks through the nested element structure. For each element it checks * the element type and widget type, extracting human-readable text from * content-bearing widgets (text-editor, heading, text, button, etc.). * * @param array $elements Array of Elementor element arrays. * @return array Flat array of extracted text strings. */ public function parse_elementor_elements( $elements ) { $texts = array(); if ( ! is_array( $elements ) ) { return $texts; } foreach ( $elements as $element ) { if ( ! is_array( $element ) ) { continue; } $el_type = isset( $element['elType'] ) ? $element['elType'] : ''; $widget_type = isset( $element['widgetType'] ) ? $element['widgetType'] : ''; $settings = isset( $element['settings'] ) ? $element['settings'] : array(); // Extract text based on widget type. if ( 'widget' === $el_type ) { switch ( $widget_type ) { case 'text-editor': if ( ! empty( $settings['editor'] ) ) { $text = wp_strip_all_tags( $settings['editor'] ); if ( ! empty( trim( $text ) ) ) { $texts[] = trim( $text ); } } break; case 'heading': if ( ! empty( $settings['title'] ) ) { $texts[] = wp_strip_all_tags( $settings['title'] ); } break; case 'text': if ( ! empty( $settings['title'] ) ) { $texts[] = wp_strip_all_tags( $settings['title'] ); } if ( ! empty( $settings['description'] ) ) { $texts[] = wp_strip_all_tags( $settings['description'] ); } break; case 'button': if ( ! empty( $settings['text'] ) ) { $texts[] = wp_strip_all_tags( $settings['text'] ); } break; case 'icon-box': case 'image-box': if ( ! empty( $settings['title_text'] ) ) { $texts[] = wp_strip_all_tags( $settings['title_text'] ); } if ( ! empty( $settings['description_text'] ) ) { $texts[] = wp_strip_all_tags( $settings['description_text'] ); } break; case 'testimonial': if ( ! empty( $settings['testimonial_content'] ) ) { $texts[] = wp_strip_all_tags( $settings['testimonial_content'] ); } if ( ! empty( $settings['testimonial_name'] ) ) { $texts[] = wp_strip_all_tags( $settings['testimonial_name'] ); } break; case 'accordion': case 'toggle': if ( ! empty( $settings['tabs'] ) && is_array( $settings['tabs'] ) ) { foreach ( $settings['tabs'] as $tab ) { if ( ! empty( $tab['tab_title'] ) ) { $texts[] = wp_strip_all_tags( $tab['tab_title'] ); } if ( ! empty( $tab['tab_content'] ) ) { $texts[] = wp_strip_all_tags( $tab['tab_content'] ); } } } break; case 'tabs': if ( ! empty( $settings['tabs'] ) && is_array( $settings['tabs'] ) ) { foreach ( $settings['tabs'] as $tab ) { if ( ! empty( $tab['tab_title'] ) ) { $texts[] = wp_strip_all_tags( $tab['tab_title'] ); } if ( ! empty( $tab['tab_content'] ) ) { $texts[] = wp_strip_all_tags( $tab['tab_content'] ); } } } break; default: // Attempt generic text extraction from common setting keys. $text_keys = array( 'title', 'description', 'editor', 'content', 'text' ); foreach ( $text_keys as $key ) { if ( ! empty( $settings[ $key ] ) && is_string( $settings[ $key ] ) ) { $text = wp_strip_all_tags( $settings[ $key ] ); if ( ! empty( trim( $text ) ) ) { $texts[] = trim( $text ); } } } break; } } // Recurse into child elements. if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) { $child_texts = $this->parse_elementor_elements( $element['elements'] ); $texts = array_merge( $texts, $child_texts ); } } return $texts; } /** * Check if Elementor is active and loaded. * * @return bool True if the 'elementor/loaded' action has fired. */ public function is_elementor_active() { return did_action( 'elementor/loaded' ) > 0; } }