*/ private function get_default_settings() { return array( 'enabled_post_types' => array( 'post' ), 'default_mode' => 'auto', 'custom_rules' => array( 'removeComments' => true, 'removeDataAttrs' => true, 'removeAriaAttrs' => true, 'removeRoleAttr' => true, 'removeStyleAttr' => true, 'removeClassOnDivs' => true, 'removeIdOnDivs' => false, 'removeEmptyAttributesEverywhere' => true, 'removeInlineSvg' => true, 'removeHiddenHtml' => true, 'removeScriptStyleBlocks' => true, 'removeEventHandlerAttrs' => true, 'removeXmlAttrs' => true, 'removeMicrosoftOfficeAttrs' => true, ), 'protected_rules' => array( 'protectShortcodes' => true, 'protectEmbeds' => true, 'protectTables' => true, 'protectImages' => true, 'protectFigures' => true, 'protectWordPressBlocks' => true, 'protectSchema' => true, ), ); } /** * Return clean-up preset labels. * * @return array */ private function get_mode_labels() { return array( 'auto' => __( 'Auto Detect', 'agizoai-html-ai-clean-up' ), 'ai_tool' => __( 'AI Tool Paste', 'agizoai-html-ai-clean-up' ), 'google_docs' => __( 'Google Docs Paste', 'agizoai-html-ai-clean-up' ), 'microsoft_word' => __( 'Microsoft Word Paste', 'agizoai-html-ai-clean-up' ), 'page_builder' => __( 'Page Builder Paste', 'agizoai-html-ai-clean-up' ), 'strict_article' => __( 'Strict Article Clean', 'agizoai-html-ai-clean-up' ), 'safe' => __( 'Safe Clean', 'agizoai-html-ai-clean-up' ), 'standard' => __( 'Standard Clean', 'agizoai-html-ai-clean-up' ), 'aggressive' => __( 'Aggressive Clean', 'agizoai-html-ai-clean-up' ), 'custom' => __( 'Custom Clean', 'agizoai-html-ai-clean-up' ), ); } /** * Return clean-up preset descriptions. * * @return array */ private function get_mode_descriptions() { return array( 'auto' => __( 'Auto Detect scans the content and chooses a suitable preset before cleaning.', 'agizoai-html-ai-clean-up' ), 'ai_tool' => __( 'AI Tool Paste targets attributes, comments, hidden fragments and wrappers commonly copied from AI chat or writing interfaces.', 'agizoai-html-ai-clean-up' ), 'google_docs' => __( 'Google Docs Paste removes rich-text noise while protecting tables, images, links and article structure.', 'agizoai-html-ai-clean-up' ), 'microsoft_word' => __( 'Microsoft Word Paste targets Office classes, XML-style attributes, inline styling and pasted formatting noise.', 'agizoai-html-ai-clean-up' ), 'page_builder' => __( 'Page Builder Paste removes noisy wrappers more carefully while protecting embeds, figures and block structure.', 'agizoai-html-ai-clean-up' ), 'strict_article' => __( 'Strict Article Clean is for editorial article HTML. It removes more formatting and keeps common content tags.', 'agizoai-html-ai-clean-up' ), 'safe' => __( 'Safe Clean removes comments, AI/tool data attributes, empty attributes and pasted script/style fragments while preserving most visible formatting.', 'agizoai-html-ai-clean-up' ), 'standard' => __( 'Standard Clean is recommended for normal AI copy-paste clean-up and removes hidden markup, inline SVG, styles and noisy wrapper attributes.', 'agizoai-html-ai-clean-up' ), 'aggressive' => __( 'Aggressive Clean removes the most markup noise and should be reviewed carefully before saving.', 'agizoai-html-ai-clean-up' ), 'custom' => __( 'Custom Clean uses the rule and protection checkboxes below.', 'agizoai-html-ai-clean-up' ), ); } /** * Return rule labels for the custom mode settings. * * @return array */ private function get_rule_labels() { return array( 'removeComments' => __( 'Remove hidden HTML comments', 'agizoai-html-ai-clean-up' ), 'removeDataAttrs' => __( 'Remove AI/tool data-* attributes', 'agizoai-html-ai-clean-up' ), 'removeAriaAttrs' => __( 'Remove copied aria-* attributes', 'agizoai-html-ai-clean-up' ), 'removeRoleAttr' => __( 'Remove copied role attributes', 'agizoai-html-ai-clean-up' ), 'removeStyleAttr' => __( 'Remove inline style attributes', 'agizoai-html-ai-clean-up' ), 'removeClassOnDivs' => __( 'Remove class attributes from div wrappers', 'agizoai-html-ai-clean-up' ), 'removeIdOnDivs' => __( 'Remove id attributes from div wrappers', 'agizoai-html-ai-clean-up' ), 'removeEmptyAttributesEverywhere' => __( 'Remove empty attributes', 'agizoai-html-ai-clean-up' ), 'removeInlineSvg' => __( 'Remove inline SVG blocks', 'agizoai-html-ai-clean-up' ), 'removeHiddenHtml' => __( 'Remove hidden and invisible HTML elements', 'agizoai-html-ai-clean-up' ), 'removeScriptStyleBlocks' => __( 'Remove pasted script, style and noscript blocks', 'agizoai-html-ai-clean-up' ), 'removeEventHandlerAttrs' => __( 'Remove JavaScript event attributes such as onclick', 'agizoai-html-ai-clean-up' ), 'removeXmlAttrs' => __( 'Remove XML and namespace attributes copied from rich editors', 'agizoai-html-ai-clean-up' ), 'removeMicrosoftOfficeAttrs' => __( 'Remove Microsoft Office classes and Office-specific attributes', 'agizoai-html-ai-clean-up' ), ); } /** * Return protection labels. * * @return array */ private function get_protection_labels() { return array( 'protectShortcodes' => __( 'Protect WordPress shortcodes', 'agizoai-html-ai-clean-up' ), 'protectEmbeds' => __( 'Protect iframe, embed, video and audio blocks', 'agizoai-html-ai-clean-up' ), 'protectTables' => __( 'Protect tables', 'agizoai-html-ai-clean-up' ), 'protectImages' => __( 'Protect image tags and alt text', 'agizoai-html-ai-clean-up' ), 'protectFigures' => __( 'Protect figure and caption blocks', 'agizoai-html-ai-clean-up' ), 'protectWordPressBlocks' => __( 'Protect WordPress block editor comments', 'agizoai-html-ai-clean-up' ), 'protectSchema' => __( 'Protect JSON-LD schema scripts', 'agizoai-html-ai-clean-up' ), ); } /** * Get saved settings merged with defaults. * * @return array */ private function get_settings() { $defaults = $this->get_default_settings(); $saved = get_option( self::OPTION_NAME, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } $settings = wp_parse_args( $saved, $defaults ); if ( ! isset( $settings['custom_rules'] ) || ! is_array( $settings['custom_rules'] ) ) { $settings['custom_rules'] = $defaults['custom_rules']; } else { $settings['custom_rules'] = wp_parse_args( $settings['custom_rules'], $defaults['custom_rules'] ); } if ( ! isset( $settings['protected_rules'] ) || ! is_array( $settings['protected_rules'] ) ) { $settings['protected_rules'] = $defaults['protected_rules']; } else { $settings['protected_rules'] = wp_parse_args( $settings['protected_rules'], $defaults['protected_rules'] ); } return $settings; } /** * Return post types that can be enabled in settings. * * @return array */ private function get_available_post_types() { $post_types = get_post_types( array( 'public' => true, ), 'objects' ); $available = array(); foreach ( $post_types as $post_type => $object ) { if ( ! post_type_exists( $post_type ) ) { continue; } $available[ sanitize_key( $post_type ) ] = isset( $object->labels->singular_name ) ? (string) $object->labels->singular_name : (string) $post_type; } if ( ! isset( $available['post'] ) ) { $available['post'] = __( 'Post', 'agizoai-html-ai-clean-up' ); } return $available; } /** * Get post types supported by the clean-up button. * * Developers may extend this list with: * add_filter( 'agizohacu_supported_post_types', function( $types ) { $types[] = 'page'; return $types; } ); * * @return string[] */ private function get_supported_post_types() { $settings = $this->get_settings(); $post_types = isset( $settings['enabled_post_types'] ) && is_array( $settings['enabled_post_types'] ) ? $settings['enabled_post_types'] : array( 'post' ); $post_types = apply_filters( 'agizohacu_supported_post_types', $post_types ); if ( ! is_array( $post_types ) ) { return array( 'post' ); } $post_types = array_map( 'sanitize_key', $post_types ); $post_types = array_filter( $post_types, 'post_type_exists' ); $post_types = array_values( array_unique( $post_types ) ); return ! empty( $post_types ) ? $post_types : array( 'post' ); } /** * Register plugin settings. * * @return void */ public function register_settings() { register_setting( 'agizohacu_settings_group', self::OPTION_NAME, array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize_settings' ), 'default' => $this->get_default_settings(), ) ); } /** * Sanitize settings submitted through the Settings API. * * @param mixed $input Raw setting input. * @return array */ public function sanitize_settings( $input ) { $defaults = $this->get_default_settings(); $available = array_keys( $this->get_available_post_types() ); $output = $defaults; if ( ! is_array( $input ) ) { return $output; } $enabled_post_types = array(); if ( isset( $input['enabled_post_types'] ) && is_array( $input['enabled_post_types'] ) ) { $enabled_post_types = array_map( 'sanitize_key', wp_unslash( $input['enabled_post_types'] ) ); $enabled_post_types = array_values( array_intersect( $enabled_post_types, $available ) ); } $output['enabled_post_types'] = ! empty( $enabled_post_types ) ? $enabled_post_types : array( 'post' ); $allowed_modes = array_keys( $this->get_mode_labels() ); $default_mode = isset( $input['default_mode'] ) ? sanitize_key( wp_unslash( $input['default_mode'] ) ) : 'auto'; $output['default_mode'] = in_array( $default_mode, $allowed_modes, true ) ? $default_mode : 'auto'; $output['custom_rules'] = array(); $submitted_custom_rules = isset( $input['custom_rules'] ) && is_array( $input['custom_rules'] ) ? wp_unslash( $input['custom_rules'] ) : array(); foreach ( $defaults['custom_rules'] as $rule_key => $default_value ) { $output['custom_rules'][ $rule_key ] = ! empty( $submitted_custom_rules[ $rule_key ] ); } $output['protected_rules'] = array(); $submitted_protected_rules = isset( $input['protected_rules'] ) && is_array( $input['protected_rules'] ) ? wp_unslash( $input['protected_rules'] ) : array(); foreach ( $defaults['protected_rules'] as $rule_key => $default_value ) { $output['protected_rules'][ $rule_key ] = ! empty( $submitted_protected_rules[ $rule_key ] ); } return $output; } /** * Register the settings page. * * @return void */ public function register_settings_page() { $this->dashboard_hook = add_menu_page( esc_html__( 'AgizoAI HTML AI Clean Up', 'agizoai-html-ai-clean-up' ), esc_html__( 'AgizoAI HTML AI Clean Up', 'agizoai-html-ai-clean-up' ), 'edit_posts', 'agizohacu-dashboard', array( $this, 'render_admin_dashboard_page' ), 'dashicons-editor-code', 58 ); add_submenu_page( 'agizohacu-dashboard', esc_html__( 'AgizoAI HTML AI Clean Up Overview', 'agizoai-html-ai-clean-up' ), esc_html__( 'Overview', 'agizoai-html-ai-clean-up' ), 'edit_posts', 'agizohacu-dashboard', array( $this, 'render_admin_dashboard_page' ) ); $this->scan_hook = add_submenu_page( 'agizohacu-dashboard', esc_html__( 'AgizoAI HTML AI Clean Up Scanner', 'agizoai-html-ai-clean-up' ), esc_html__( 'HTML Scan', 'agizoai-html-ai-clean-up' ), 'edit_posts', 'agizohacu-site-scan', array( $this, 'render_site_scan_page' ) ); $this->settings_hook = add_submenu_page( 'agizohacu-dashboard', esc_html__( 'AgizoAI HTML AI Clean Up Settings', 'agizoai-html-ai-clean-up' ), esc_html__( 'Settings', 'agizoai-html-ai-clean-up' ), 'manage_options', 'agizohacu-settings', array( $this, 'render_settings_page' ) ); } /** * Add settings link on the Plugins screen. * * @param string[] $links Existing plugin action links. * @return string[] */ public function add_settings_link( $links ) { $settings_link = sprintf( '%2$s', esc_url( admin_url( 'admin.php?page=agizohacu-settings' ) ), esc_html__( 'Settings', 'agizoai-html-ai-clean-up' ) ); array_unshift( $links, $settings_link ); return $links; } /** * Render the plugin umbrella dashboard page. * * @return void */ public function render_admin_dashboard_page() { if ( ! current_user_can( 'edit_posts' ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'agizoai-html-ai-clean-up' ) ); } $settings_url = admin_url( 'admin.php?page=agizohacu-settings' ); $scan_url = admin_url( 'admin.php?page=agizohacu-site-scan' ); ?>

get_settings(); $available_types = $this->get_available_post_types(); $enabled_types = isset( $settings['enabled_post_types'] ) && is_array( $settings['enabled_post_types'] ) ? $settings['enabled_post_types'] : array( 'post' ); $default_mode = isset( $settings['default_mode'] ) ? (string) $settings['default_mode'] : 'auto'; $custom_rules = isset( $settings['custom_rules'] ) && is_array( $settings['custom_rules'] ) ? $settings['custom_rules'] : $this->get_default_settings()['custom_rules']; $protected_rules = isset( $settings['protected_rules'] ) && is_array( $settings['protected_rules'] ) ? $settings['protected_rules'] : $this->get_default_settings()['protected_rules']; $rule_labels = $this->get_rule_labels(); $protection_labels = $this->get_protection_labels(); $mode_labels = $this->get_mode_labels(); $mode_descriptions = $this->get_mode_descriptions(); ?>

get_supported_post_types() as $post_type ) { add_meta_box( 'agizohacu_clean_html_meta_box', esc_html__( 'HTML AI Clean Up', 'agizoai-html-ai-clean-up' ), array( $this, 'render_meta_box' ), $post_type, 'side', 'high' ); } } /** * Render the editor meta box. * * @param WP_Post $post Current post object. * @return void */ public function render_meta_box( $post ) { if ( ! $post instanceof WP_Post || ! current_user_can( 'edit_post', $post->ID ) ) { echo '

' . esc_html__( 'You do not have permission to use this tool for this content.', 'agizoai-html-ai-clean-up' ) . '

'; return; } $settings = $this->get_settings(); $default_mode = isset( $settings['default_mode'] ) ? (string) $settings['default_mode'] : 'auto'; $mode_labels = $this->get_mode_labels(); wp_nonce_field( 'agizohacu_clean_html_action', 'agizohacu_clean_html_nonce' ); ?>

get_available_post_types(); $statuses = $this->get_scan_statuses(); $mode_labels = $this->get_mode_labels(); ?>

*/ private function get_scan_statuses() { return array( 'any' => __( 'Recent editable content', 'agizoai-html-ai-clean-up' ), 'publish' => __( 'Published', 'agizoai-html-ai-clean-up' ), 'draft' => __( 'Drafts', 'agizoai-html-ai-clean-up' ), 'pending' => __( 'Pending review', 'agizoai-html-ai-clean-up' ), 'future' => __( 'Scheduled', 'agizoai-html-ai-clean-up' ), 'private' => __( 'Private', 'agizoai-html-ai-clean-up' ), ); } /** * AJAX endpoint for the read-only site-wide scanner. * * @return void */ public function ajax_scan_posts() { if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to scan content.', 'agizoai-html-ai-clean-up' ), ), 403 ); } check_ajax_referer( 'agizohacu_site_scan', 'nonce' ); $available_post_types = array_keys( $this->get_available_post_types() ); $allowed_statuses = array_keys( $this->get_scan_statuses() ); $raw_post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : 'post'; $post_type = in_array( $raw_post_type, $available_post_types, true ) ? $raw_post_type : 'post'; $raw_status = isset( $_POST['post_status'] ) ? sanitize_key( wp_unslash( $_POST['post_status'] ) ) : 'any'; $status = in_array( $raw_status, $allowed_statuses, true ) ? $raw_status : 'any'; $limit = isset( $_POST['limit'] ) ? absint( wp_unslash( $_POST['limit'] ) ) : 20; $limit = min( max( $limit, 1 ), 50 ); $post_status = 'any' === $status ? array( 'publish', 'draft', 'pending', 'future', 'private' ) : $status; $query = new WP_Query( array( 'post_type' => $post_type, 'post_status' => $post_status, 'posts_per_page' => $limit, 'orderby' => 'modified', 'order' => 'DESC', 'fields' => 'ids', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); $results = array(); $scanned = 0; $flagged = 0; $risk_summary = array( 'clean' => 0, 'low' => 0, 'medium' => 0, 'high' => 0, ); foreach ( $query->posts as $post_id ) { $post_id = absint( $post_id ); if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) { continue; } $post = get_post( $post_id ); if ( ! $post instanceof WP_Post ) { continue; } $content = (string) $post->post_content; $scan = $this->scan_content_for_markup( $content ); $quality = $this->analyse_editor_quality( $content ); $scanned += 1; if ( $scan['total'] > 0 ) { $flagged += 1; } if ( isset( $risk_summary[ $scan['risk'] ] ) ) { $risk_summary[ $scan['risk'] ] += 1; } $results[] = array( 'id' => $post_id, 'title' => get_the_title( $post_id ), 'status' => get_post_status( $post_id ), 'modified' => get_post_modified_time( 'Y-m-d H:i', false, $post_id, true ), 'editUrl' => get_edit_post_link( $post_id, 'raw' ), 'total' => $scan['total'], 'score' => $scan['score'], 'risk' => $scan['risk'], 'recommended' => $scan['recommended'], 'items' => $scan['items'], 'quality' => $quality, ); } wp_send_json_success( array( 'scanned' => $scanned, 'flagged' => $flagged, 'riskSummary' => $risk_summary, 'results' => $results, ) ); } /** * AJAX endpoint for revision-safe selected post clean-up. * * @return void */ public function ajax_bulk_clean_posts() { if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to clean content.', 'agizoai-html-ai-clean-up' ), ), 403 ); } check_ajax_referer( 'agizohacu_bulk_clean', 'nonce' ); $post_ids = isset( $_POST['post_ids'] ) && is_array( $_POST['post_ids'] ) ? array_values( array_unique( array_map( 'absint', wp_unslash( $_POST['post_ids'] ) ) ) ) : array(); $post_ids = array_slice( array_filter( $post_ids ), 0, 20 ); $allowed_modes = array_keys( $this->get_mode_labels() ); $raw_mode = isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( $_POST['mode'] ) ) : 'auto'; $mode = in_array( $raw_mode, $allowed_modes, true ) ? $raw_mode : 'auto'; if ( empty( $post_ids ) ) { wp_send_json_error( array( 'message' => __( 'No posts were selected for clean-up.', 'agizoai-html-ai-clean-up' ), ), 400 ); } $results = array(); $updated = 0; $skipped = 0; $failed = 0; foreach ( $post_ids as $post_id ) { if ( ! current_user_can( 'edit_post', $post_id ) ) { $failed++; $results[] = array( 'id' => $post_id, 'title' => '', 'status' => 'error', 'message' => __( 'Permission denied for this post.', 'agizoai-html-ai-clean-up' ), ); continue; } $post = get_post( $post_id ); if ( ! $post instanceof WP_Post ) { $failed++; $results[] = array( 'id' => $post_id, 'title' => '', 'status' => 'error', 'message' => __( 'Post not found.', 'agizoai-html-ai-clean-up' ), ); continue; } $before_content = (string) $post->post_content; $before_scan = $this->scan_content_for_markup( $before_content ); $effective_mode = 'auto' === $mode && ! empty( $before_scan['recommended'] ) ? sanitize_key( $before_scan['recommended'] ) : $mode; $clean_content = $this->clean_content_for_bulk_update( $before_content, $effective_mode ); if ( $clean_content === $before_content ) { $skipped++; $results[] = array( 'id' => $post_id, 'title' => get_the_title( $post_id ), 'status' => 'unchanged', 'message' => __( 'No removable markup found for the selected preset.', 'agizoai-html-ai-clean-up' ), 'beforeTotal' => $before_scan['total'], 'afterTotal' => $before_scan['total'], 'mode' => $effective_mode, 'editUrl' => get_edit_post_link( $post_id, 'raw' ), ); continue; } $revision_created = false; if ( post_type_supports( $post->post_type, 'revisions' ) && function_exists( 'wp_save_post_revision' ) ) { $revision_id = wp_save_post_revision( $post_id ); $revision_created = is_numeric( $revision_id ) && $revision_id > 0; } $update_result = wp_update_post( wp_slash( array( 'ID' => $post_id, 'post_content' => $clean_content, ) ), true ); if ( is_wp_error( $update_result ) ) { $failed++; $results[] = array( 'id' => $post_id, 'title' => get_the_title( $post_id ), 'status' => 'error', 'message' => $update_result->get_error_message(), 'beforeTotal' => $before_scan['total'], 'afterTotal' => $before_scan['total'], 'mode' => $effective_mode, 'editUrl' => get_edit_post_link( $post_id, 'raw' ), ); continue; } $after_scan = $this->scan_content_for_markup( $clean_content ); $updated++; $results[] = array( 'id' => $post_id, 'title' => get_the_title( $post_id ), 'status' => 'updated', 'message' => __( 'Post cleaned and saved with revision backup where supported.', 'agizoai-html-ai-clean-up' ), 'beforeTotal' => $before_scan['total'], 'afterTotal' => $after_scan['total'], 'mode' => $effective_mode, 'revisionCreated' => $revision_created, 'editUrl' => get_edit_post_link( $post_id, 'raw' ), ); } wp_send_json_success( array( 'updated' => $updated, 'skipped' => $skipped, 'failed' => $failed, 'results' => $results, ) ); } /** * Clean content on the server for revision-safe selected post updates. * * @param string $content Content to clean. * @param string $mode Clean-up preset. * @return string */ private function clean_content_for_bulk_update( $content, $mode ) { $settings = $this->get_settings(); $protections = isset( $settings['protected_rules'] ) && is_array( $settings['protected_rules'] ) ? $settings['protected_rules'] : $this->get_default_settings()['protected_rules']; $rules = $this->get_bulk_clean_rules_for_mode( $mode ); $protected = $this->protect_bulk_fragments( $content, $protections ); $content = $protected['content']; if ( ! empty( $rules['removeComments'] ) ) { $content = preg_replace( '//is', '', $content ); } if ( ! empty( $rules['removeScriptStyleBlocks'] ) ) { $content = preg_replace( '/<(script|style|noscript)\b[^>]*>.*?<\/\1>/is', '', $content ); } if ( ! empty( $rules['removeInlineSvg'] ) ) { $content = preg_replace( '/]*>.*?<\/svg>/is', '', $content ); } if ( ! empty( $rules['removeHiddenHtml'] ) ) { $content = preg_replace( '/]*type=["\']?hidden["\']?[^>]*>/is', '', $content ); $content = preg_replace( '/<([a-z][a-z0-9]*)\b[^>]*(?:\shidden\b|\sstyle=["\'][^"\']*(?:display\s*:\s*none|visibility\s*:\s*hidden)[^"\']*["\'])[^>]*>.*?<\/\1>/is', '', $content ); $content = preg_replace( '/<[^>]+(?:\shidden\b|\sstyle=["\'][^"\']*(?:display\s*:\s*none|visibility\s*:\s*hidden)[^"\']*["\'])[^>]*>/is', '', $content ); } if ( ! empty( $rules['removeDataAttrs'] ) ) { $content = $this->remove_bulk_attribute_pattern( $content, 'data-[a-z0-9_-]+' ); } if ( ! empty( $rules['removeAriaAttrs'] ) ) { $content = $this->remove_bulk_attribute_pattern( $content, 'aria-[a-z0-9_-]+' ); } if ( ! empty( $rules['removeRoleAttr'] ) ) { $content = $this->remove_bulk_attribute_pattern( $content, 'role' ); } if ( ! empty( $rules['removeStyleAttr'] ) ) { $content = $this->remove_bulk_attribute_pattern( $content, 'style' ); } if ( ! empty( $rules['removeEventHandlerAttrs'] ) ) { $content = $this->remove_bulk_attribute_pattern( $content, 'on[a-z]+' ); } if ( ! empty( $rules['removeXmlAttrs'] ) ) { $content = $this->remove_bulk_attribute_pattern( $content, '(?:xmlns(?::[a-z0-9_-]+)?|xml:[a-z0-9_-]+|[a-z0-9_-]+:[a-z0-9_-]+)' ); } if ( ! empty( $rules['removeMicrosoftOfficeAttrs'] ) ) { $content = preg_replace( '/\s(?:class|style)\s*=\s*(["\'])[^"\']*(?:Mso|mso-|WordSection|Apple-converted-space)[^"\']*\1/i', '', $content ); $content = $this->remove_bulk_attribute_pattern( $content, '(?:lang|v:[a-z0-9_-]+|o:[a-z0-9_-]+|w:[a-z0-9_-]+)' ); } if ( ! empty( $rules['removeClassOnDivs'] ) ) { $content = $this->remove_bulk_div_attribute( $content, 'class' ); } if ( ! empty( $rules['removeIdOnDivs'] ) ) { $content = $this->remove_bulk_div_attribute( $content, 'id' ); } if ( ! empty( $rules['removeEmptyAttributesEverywhere'] ) ) { $content = preg_replace( '/\s[a-zA-Z_:][-a-zA-Z0-9_:.]*\s*=\s*(["\'])\s*\1/', '', $content ); } if ( 'strict_article' === $mode ) { $content = wp_kses( $content, $this->get_strict_article_allowed_html() ); } $content = $this->restore_bulk_fragments( (string) $content, $protected['fragments'] ); $content = preg_replace( "/\n{3,}/", "\n\n", (string) $content ); return trim( (string) $content ); } /** * Return server-side clean-up rules for a preset. * * @param string $mode Preset key. * @return array */ private function get_bulk_clean_rules_for_mode( $mode ) { $defaults = $this->get_default_settings()['custom_rules']; $off = array_fill_keys( array_keys( $defaults ), false ); switch ( $mode ) { case 'safe': return wp_parse_args( array( 'removeComments' => true, 'removeDataAttrs' => true, 'removeEmptyAttributesEverywhere' => true, 'removeScriptStyleBlocks' => true, ), $off ); case 'ai_tool': return wp_parse_args( array( 'removeComments' => true, 'removeDataAttrs' => true, 'removeAriaAttrs' => true, 'removeRoleAttr' => true, 'removeStyleAttr' => true, 'removeClassOnDivs' => true, 'removeEmptyAttributesEverywhere' => true, 'removeInlineSvg' => true, 'removeHiddenHtml' => true, 'removeScriptStyleBlocks' => true, 'removeEventHandlerAttrs' => true, ), $off ); case 'google_docs': return wp_parse_args( array( 'removeComments' => true, 'removeStyleAttr' => true, 'removeClassOnDivs' => true, 'removeEmptyAttributesEverywhere' => true, ), $off ); case 'microsoft_word': return wp_parse_args( array( 'removeComments' => true, 'removeStyleAttr' => true, 'removeClassOnDivs' => true, 'removeEmptyAttributesEverywhere' => true, 'removeScriptStyleBlocks' => true, 'removeXmlAttrs' => true, 'removeMicrosoftOfficeAttrs' => true, ), $off ); case 'page_builder': return wp_parse_args( array( 'removeComments' => true, 'removeDataAttrs' => true, 'removeEmptyAttributesEverywhere' => true, 'removeEventHandlerAttrs' => true, ), $off ); case 'strict_article': return wp_parse_args( array( 'removeComments' => true, 'removeDataAttrs' => true, 'removeAriaAttrs' => true, 'removeRoleAttr' => true, 'removeStyleAttr' => true, 'removeClassOnDivs' => true, 'removeIdOnDivs' => true, 'removeEmptyAttributesEverywhere' => true, 'removeInlineSvg' => true, 'removeHiddenHtml' => true, 'removeScriptStyleBlocks' => true, 'removeEventHandlerAttrs' => true, 'removeXmlAttrs' => true, 'removeMicrosoftOfficeAttrs' => true, ), $off ); case 'aggressive': $rules = $defaults; $rules['removeIdOnDivs'] = true; return $rules; case 'custom': $settings = $this->get_settings(); return isset( $settings['custom_rules'] ) && is_array( $settings['custom_rules'] ) ? wp_parse_args( $settings['custom_rules'], $defaults ) : $defaults; case 'standard': default: return $defaults; } } /** * Remove matching attributes from HTML text. * * @param string $content HTML content. * @param string $attribute_pattern Attribute name regex. * @return string */ private function remove_bulk_attribute_pattern( $content, $attribute_pattern ) { $pattern = '/\s' . $attribute_pattern . '(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+))?/i'; return (string) preg_replace( $pattern, '', $content ); } /** * Remove an attribute only from div opening tags. * * @param string $content HTML content. * @param string $attribute Attribute name. * @return string */ private function remove_bulk_div_attribute( $content, $attribute ) { $attribute = preg_quote( $attribute, '/' ); return (string) preg_replace_callback( '/]*>/i', function ( $matches ) use ( $attribute ) { return $this->remove_bulk_attribute_pattern( $matches[0], $attribute ); }, $content ); } /** * Protect useful fragments with placeholders before server-side cleaning. * * @param string $content Content to protect. * @param array $protections Protection settings. * @return array{content:string,fragments:array} */ private function protect_bulk_fragments( $content, $protections ) { $fragments = array(); $index = 0; $protect = function ( $pattern ) use ( &$content, &$fragments, &$index ) { $content = (string) preg_replace_callback( $pattern, function ( $matches ) use ( &$fragments, &$index ) { $key = '%%AGIZOHACU_PROTECTED_' . $index . '%%'; $fragments[ $key ] = $matches[0]; $index++; return $key; }, $content ); }; if ( ! empty( $protections['protectSchema'] ) ) { $schema_script_pattern = '/<' . 'script\b[^>]*type=["\']application\/ld\+json["\'][^>]*>.*?<\/' . 'script>/is'; $protect( $schema_script_pattern ); } if ( ! empty( $protections['protectWordPressBlocks'] ) ) { $protect( '//is' ); } if ( ! empty( $protections['protectEmbeds'] ) ) { $protect( '/<(iframe|video|audio)\b[^>]*>.*?<\/\1>|]*>/is' ); } if ( ! empty( $protections['protectTables'] ) ) { $protect( '/]*>.*?<\/table>/is' ); } if ( ! empty( $protections['protectFigures'] ) ) { $protect( '/]*>.*?<\/figure>/is' ); } if ( ! empty( $protections['protectImages'] ) ) { $protect( '/]*>/is' ); } if ( ! empty( $protections['protectShortcodes'] ) && function_exists( 'get_shortcode_regex' ) ) { $shortcode_regex = get_shortcode_regex(); if ( is_string( $shortcode_regex ) && '' !== $shortcode_regex ) { $protect( '/' . $shortcode_regex . '/s' ); } } return array( 'content' => $content, 'fragments' => $fragments, ); } /** * Restore protected placeholders. * * @param string $content Content with placeholders. * @param array $fragments Protected fragments. * @return string */ private function restore_bulk_fragments( $content, $fragments ) { return ! empty( $fragments ) ? strtr( $content, $fragments ) : $content; } /** * Return allowed HTML for Strict Article Clean. * * @return array> */ private function get_strict_article_allowed_html() { $global = array( 'class' => true, ); return array( 'p' => $global, 'h1' => $global, 'h2' => $global, 'h3' => $global, 'h4' => $global, 'h5' => $global, 'h6' => $global, 'strong' => array(), 'b' => array(), 'em' => array(), 'i' => array(), 'u' => array(), 'ul' => $global, 'ol' => $global, 'li' => $global, 'a' => array( 'href' => true, 'title' => true, 'target' => true, 'rel' => true, ), 'img' => array( 'src' => true, 'alt' => true, 'title' => true, 'width' => true, 'height' => true, 'class' => true, ), 'figure' => $global, 'figcaption' => $global, 'blockquote' => array( 'cite' => true, 'class' => true, ), 'cite' => array(), 'table' => $global, 'thead' => array(), 'tbody' => array(), 'tfoot' => array(), 'tr' => array(), 'th' => array( 'colspan' => true, 'rowspan' => true, 'scope' => true, ), 'td' => array( 'colspan' => true, 'rowspan' => true, ), 'br' => array(), 'hr' => array(), 'pre' => array(), 'code' => array(), 'span' => $global, ); } /** * Scan a content string for common AI/paste markup. This is read-only. * * @param string $content Post content. * @return array */ private function scan_content_for_markup( $content ) { $items = array( 'comments' => $this->count_pattern( '//is', $content ), 'inlineSvg' => $this->count_pattern( '/]*>.*?<\/svg>/is', $content ), 'scriptStyleBlocks' => $this->count_pattern( '/<(script|style|noscript)\b[^>]*>.*?<\/\1>/is', $content ), 'hiddenInputs' => $this->count_pattern( '/]*type=["\']?hidden["\']?[^>]*>/is', $content ), 'hiddenElements' => $this->count_pattern( '/<[^>]+\s(?:hidden|style=["\'][^"\']*(?:display\s*:\s*none|visibility\s*:\s*hidden)[^"\']*["\'])[^>]*>/is', $content ), 'dataAttrs' => $this->count_pattern( '/\sdata-[a-z0-9_-]+\s*=/i', $content ), 'ariaAttrs' => $this->count_pattern( '/\saria-[a-z0-9_-]+\s*=/i', $content ), 'roleAttrs' => $this->count_pattern( '/\srole\s*=/i', $content ), 'styleAttrs' => $this->count_pattern( '/\sstyle\s*=/i', $content ), 'classOnDivs' => $this->count_pattern( '/]*\sclass\s*=/i', $content ), 'idOnDivs' => $this->count_pattern( '/]*\sid\s*=/i', $content ), 'emptyAttrs' => $this->count_pattern( '/\s[a-zA-Z_:][-a-zA-Z0-9_:.]*\s*=\s*(["\'])\s*\1/', $content ), 'eventHandlerAttrs' => $this->count_pattern( '/\son[a-z]+\s*=/i', $content ), 'xmlAttrs' => $this->count_pattern( '/\s(?:xmlns(?::[a-z0-9_-]+)?|xml:[a-z0-9_-]+|[a-z0-9_-]+:[a-z0-9_-]+)\s*=/i', $content ), 'msOfficeAttrs' => $this->count_pattern( '/\s(?:class|style)=["\'][^"\']*(?:Mso|mso-|WordSection|Apple-converted-space)[^"\']*["\']|\s(?:lang|v:|o:|w:)\w*\s*=/i', $content ), ); $total = array_sum( $items ); $score = ( $items['comments'] * 1 ) + ( $items['inlineSvg'] * 3 ) + ( $items['scriptStyleBlocks'] * 8 ) + ( $items['hiddenInputs'] * 5 ) + ( $items['hiddenElements'] * 5 ) + ( $items['dataAttrs'] * 1 ) + ( $items['ariaAttrs'] * 1 ) + ( $items['roleAttrs'] * 1 ) + ( $items['styleAttrs'] * 1 ) + ( $items['classOnDivs'] * 1 ) + ( $items['idOnDivs'] * 1 ) + ( $items['emptyAttrs'] * 1 ) + ( $items['eventHandlerAttrs'] * 5 ) + ( $items['xmlAttrs'] * 2 ) + ( $items['msOfficeAttrs'] * 2 ); $risk = 'clean'; if ( $score > 25 ) { $risk = 'high'; } elseif ( $score > 8 ) { $risk = 'medium'; } elseif ( $score > 0 ) { $risk = 'low'; } $recommended = 'safe'; if ( $items['msOfficeAttrs'] > 0 || $items['xmlAttrs'] > 3 ) { $recommended = 'microsoft_word'; } elseif ( $items['scriptStyleBlocks'] > 0 || $items['eventHandlerAttrs'] > 0 || $items['hiddenElements'] > 2 || $items['hiddenInputs'] > 0 ) { $recommended = 'aggressive'; } elseif ( $items['dataAttrs'] > 0 || $items['comments'] > 0 || $items['inlineSvg'] > 0 ) { $recommended = 'ai_tool'; } elseif ( $items['styleAttrs'] > 0 || $items['classOnDivs'] > 0 ) { $recommended = 'google_docs'; } return array( 'items' => array_filter( $items ), 'total' => $total, 'score' => $score, 'risk' => $risk, 'recommended' => $recommended, ); } /** * Analyse content for editor HTML quality, accessibility and SEO-safe structure. * * @param string $content Post content. * @return array */ private function analyse_editor_quality( $content ) { $content = (string) $content; $text = trim( wp_strip_all_tags( $content ) ); $words = str_word_count( $text ); $score = 100; $accessibility = array(); $seo = array(); $html = array(); $missing_alt = $this->count_pattern( '/]*\salt\s*=)[^>]*>/i', $content ); if ( $missing_alt > 0 ) { $accessibility['images_missing_alt'] = $missing_alt; $seo['images_missing_alt'] = $missing_alt; $score -= min( 18, $missing_alt * 6 ); } $empty_alt = $this->count_pattern( '/]*\salt\s*=\s*(["\'])\s*\1[^>]*>/i', $content ); if ( $empty_alt > 0 ) { $accessibility['images_empty_alt'] = $empty_alt; $score -= min( 8, $empty_alt * 2 ); } $iframe_missing_title = $this->count_pattern( '/]*\stitle\s*=)[^>]*>/i', $content ); if ( $iframe_missing_title > 0 ) { $accessibility['iframes_missing_title'] = $iframe_missing_title; $score -= min( 12, $iframe_missing_title * 6 ); } $empty_links = $this->count_pattern( '/]*href\s*=\s*(["\']).*?\1[^>]*>\s*(?:<[^>]+>\s*)*<\/a>/is', $content ); if ( $empty_links > 0 ) { $accessibility['empty_links'] = $empty_links; $seo['empty_links'] = $empty_links; $score -= min( 15, $empty_links * 5 ); } $weak_link_text = $this->count_pattern( '/]*>\s*(?:click here|read more|more|link)\s*<\/a>/i', $content ); if ( $weak_link_text > 0 ) { $accessibility['weak_link_text'] = $weak_link_text; $seo['weak_link_text'] = $weak_link_text; $score -= min( 8, $weak_link_text * 3 ); } $bad_links = $this->count_pattern( '/]*href\s*=\s*(["\'])(?:#|javascript:|)\1/i', $content ); if ( $bad_links > 0 ) { $seo['unsafe_or_empty_links'] = $bad_links; $score -= min( 12, $bad_links * 4 ); } $tables_without_headers = $this->count_pattern( '//i', $content ); if ( $tables_without_headers > 0 ) { $accessibility['tables_without_headers'] = $tables_without_headers; $score -= min( 10, $tables_without_headers * 5 ); } $duplicate_ids = 0; if ( preg_match_all( '/\sid\s*=\s*(["\'])([^"\']+)\1/i', $content, $id_matches ) ) { $id_counts = array_count_values( array_map( 'strtolower', $id_matches[2] ) ); foreach ( $id_counts as $count ) { if ( $count > 1 ) { $duplicate_ids += ( $count - 1 ); } } } if ( $duplicate_ids > 0 ) { $accessibility['duplicate_ids'] = $duplicate_ids; $html['duplicate_ids'] = $duplicate_ids; $score -= min( 12, $duplicate_ids * 4 ); } $heading_jumps = 0; if ( preg_match_all( '/]*>/i', $content, $heading_matches ) ) { $previous = 0; foreach ( $heading_matches[1] as $level ) { $level = absint( $level ); if ( $previous > 0 && $level > ( $previous + 1 ) ) { $heading_jumps++; } $previous = $level; } } if ( $heading_jumps > 0 ) { $accessibility['heading_level_jumps'] = $heading_jumps; $seo['heading_level_jumps'] = $heading_jumps; $score -= min( 12, $heading_jumps * 4 ); } $h1_count = $this->count_pattern( '/ 0 ) { $seo['h1_in_post_content'] = $h1_count; $score -= min( 8, $h1_count * 3 ); } $heading_count = $this->count_pattern( '/ 350 && 0 === $heading_count ) { $seo['long_content_without_headings'] = 1; $score -= 8; } $long_paragraphs = 0; if ( preg_match_all( '/]*>(.*?)<\/p>/is', $content, $paragraphs ) ) { foreach ( $paragraphs[1] as $paragraph ) { if ( str_word_count( wp_strip_all_tags( $paragraph ) ) > 130 ) { $long_paragraphs++; } } } if ( $long_paragraphs > 0 ) { $seo['long_paragraphs'] = $long_paragraphs; $score -= min( 8, $long_paragraphs * 2 ); } $empty_blocks = $this->count_pattern( '/<(p|div|span)\b[^>]*>\s*(?: |)?\s*<\/\1>/i', $content ); if ( $empty_blocks > 0 ) { $html['empty_blocks'] = $empty_blocks; $score -= min( 10, $empty_blocks * 2 ); } $div_span_count = $this->count_pattern( '/<(div|span)\b/i', $content ); $semantic_count = max( 1, $this->count_pattern( '/<(p|h[1-6]|ul|ol|li|blockquote|figure|table)\b/i', $content ) ); if ( $div_span_count > ( $semantic_count * 3 ) ) { $html['too_many_wrappers'] = $div_span_count; $score -= 8; } $markdown_artifacts = $this->count_pattern( '/(^|\s)(?:#{2,6}\s|\*\*[^*]+\*\*|```)/m', $content ); if ( $markdown_artifacts > 0 ) { $seo['markdown_artifacts'] = $markdown_artifacts; $html['markdown_artifacts'] = $markdown_artifacts; $score -= min( 10, $markdown_artifacts * 3 ); } if ( $words > 0 && $words < 120 ) { $seo['very_short_content'] = 1; $score -= 4; } $quality_level = 'excellent'; if ( $score < 60 ) { $quality_level = 'poor'; } elseif ( $score < 75 ) { $quality_level = 'needs_review'; } elseif ( $score < 90 ) { $quality_level = 'good'; } return array( 'score' => max( 0, min( 100, absint( $score ) ) ), 'level' => $quality_level, 'wordCount' => absint( $words ), 'accessibility' => array_filter( $accessibility ), 'seo' => array_filter( $seo ), 'html' => array_filter( $html ), 'warningCount' => count( array_filter( $accessibility ) ) + count( array_filter( $seo ) ) + count( array_filter( $html ) ), ); } /** * Count regex matches safely. * * @param string $pattern Regular expression. * @param string $subject Content to scan. * @return int */ private function count_pattern( $pattern, $subject ) { $count = preg_match_all( $pattern, $subject, $matches ); return false === $count ? 0 : absint( $count ); } /** * Enqueue admin assets only on settings and supported editor screens. * * @param string $hook Current admin page hook. * @return void */ public function enqueue_admin_assets( $hook ) { $plugin_admin_hooks = array_filter( array( $this->dashboard_hook, $this->settings_hook, $this->scan_hook ) ); if ( in_array( $hook, $plugin_admin_hooks, true ) ) { wp_enqueue_style( 'agizohacu-admin', AGIZOHACU_URL . 'assets/css/agizohacu-admin.css', array(), AGIZOHACU_VERSION ); } if ( $hook === $this->dashboard_hook || $hook === $this->settings_hook ) { return; } if ( $hook === $this->scan_hook ) { wp_enqueue_script( 'agizohacu-tools', AGIZOHACU_URL . 'assets/js/agizohacu-tools.js', array(), AGIZOHACU_VERSION, true ); wp_localize_script( 'agizohacu-tools', 'agizohacuSiteScan', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'agizohacu_site_scan' ), 'action' => 'agizohacu_scan_posts', 'bulkAction' => 'agizohacu_bulk_clean_posts', 'bulkNonce' => wp_create_nonce( 'agizohacu_bulk_clean' ), 'modeLabels' => array_map( 'esc_html', $this->get_mode_labels() ), 'reportLabels' => array( 'comments' => esc_html__( 'HTML comments', 'agizoai-html-ai-clean-up' ), 'inlineSvg' => esc_html__( 'inline SVG blocks', 'agizoai-html-ai-clean-up' ), 'scriptStyleBlocks' => esc_html__( 'script, style or noscript blocks', 'agizoai-html-ai-clean-up' ), 'hiddenInputs' => esc_html__( 'hidden input fields', 'agizoai-html-ai-clean-up' ), 'hiddenElements' => esc_html__( 'hidden or invisible elements', 'agizoai-html-ai-clean-up' ), 'dataAttrs' => esc_html__( 'data-* attributes', 'agizoai-html-ai-clean-up' ), 'ariaAttrs' => esc_html__( 'aria-* attributes', 'agizoai-html-ai-clean-up' ), 'roleAttrs' => esc_html__( 'role attributes', 'agizoai-html-ai-clean-up' ), 'styleAttrs' => esc_html__( 'style attributes', 'agizoai-html-ai-clean-up' ), 'classOnDivs' => esc_html__( 'class attributes on div wrappers', 'agizoai-html-ai-clean-up' ), 'idOnDivs' => esc_html__( 'id attributes on div wrappers', 'agizoai-html-ai-clean-up' ), 'emptyAttrs' => esc_html__( 'empty attributes', 'agizoai-html-ai-clean-up' ), 'eventHandlerAttrs' => esc_html__( 'JavaScript event attributes', 'agizoai-html-ai-clean-up' ), 'xmlAttrs' => esc_html__( 'XML or namespace attributes', 'agizoai-html-ai-clean-up' ), 'msOfficeAttrs' => esc_html__( 'Microsoft Office formatting attributes/classes', 'agizoai-html-ai-clean-up' ), ), 'riskLabels' => array( 'clean' => esc_html__( 'Clean', 'agizoai-html-ai-clean-up' ), 'low' => esc_html__( 'Low', 'agizoai-html-ai-clean-up' ), 'medium' => esc_html__( 'Medium', 'agizoai-html-ai-clean-up' ), 'high' => esc_html__( 'High', 'agizoai-html-ai-clean-up' ), ), 'qualityLabels' => array( 'excellent' => esc_html__( 'Excellent', 'agizoai-html-ai-clean-up' ), 'good' => esc_html__( 'Good', 'agizoai-html-ai-clean-up' ), 'needs_review' => esc_html__( 'Needs review', 'agizoai-html-ai-clean-up' ), 'poor' => esc_html__( 'Poor', 'agizoai-html-ai-clean-up' ), ), 'messages' => array( 'scanning' => esc_html__( 'Scanning content. Please wait...', 'agizoai-html-ai-clean-up' ), 'complete' => esc_html__( 'Site-wide scan complete.', 'agizoai-html-ai-clean-up' ), 'failed' => esc_html__( 'Scan failed. Please refresh the page and try again.', 'agizoai-html-ai-clean-up' ), 'none' => esc_html__( 'No posts were found for this scan.', 'agizoai-html-ai-clean-up' ), 'openEditor' => esc_html__( 'Open editor', 'agizoai-html-ai-clean-up' ), 'itemsDetected' => esc_html__( 'items detected', 'agizoai-html-ai-clean-up' ), 'selectLabel' => esc_html__( 'Select', 'agizoai-html-ai-clean-up' ), 'selectAll' => esc_html__( 'Select all flagged posts', 'agizoai-html-ai-clean-up' ), 'cleaning' => esc_html__( 'Cleaning selected posts. Please wait...', 'agizoai-html-ai-clean-up' ), 'cleanComplete' => esc_html__( 'Selected post clean-up complete.', 'agizoai-html-ai-clean-up' ), 'cleanFailed' => esc_html__( 'Selected post clean-up failed. Please refresh the page and try again.', 'agizoai-html-ai-clean-up' ), 'noSelection' => esc_html__( 'Select at least one scanned post to clean.', 'agizoai-html-ai-clean-up' ), 'confirmBulk' => esc_html__( 'Clean the selected posts now? A revision backup will be created before each changed post is saved where revisions are supported.', 'agizoai-html-ai-clean-up' ), 'updatedLabel' => esc_html__( 'Updated', 'agizoai-html-ai-clean-up' ), 'skippedLabel' => esc_html__( 'Skipped', 'agizoai-html-ai-clean-up' ), 'failedLabel' => esc_html__( 'Failed', 'agizoai-html-ai-clean-up' ), 'revisionLabel' => esc_html__( 'Revision backup', 'agizoai-html-ai-clean-up' ), 'yesLabel' => esc_html__( 'Yes', 'agizoai-html-ai-clean-up' ), 'noLabel' => esc_html__( 'No', 'agizoai-html-ai-clean-up' ), 'beforeLabel' => esc_html__( 'Before', 'agizoai-html-ai-clean-up' ), 'afterLabel' => esc_html__( 'After', 'agizoai-html-ai-clean-up' ), 'scannedLabel' => esc_html__( 'Scanned', 'agizoai-html-ai-clean-up' ), 'flaggedLabel' => esc_html__( 'Needs review', 'agizoai-html-ai-clean-up' ), 'riskLabel' => esc_html__( 'Risk', 'agizoai-html-ai-clean-up' ), 'presetLabel' => esc_html__( 'Recommended preset', 'agizoai-html-ai-clean-up' ), 'qualityLabel' => esc_html__( 'HTML quality', 'agizoai-html-ai-clean-up' ), 'warningLabel' => esc_html__( 'Quality warnings', 'agizoai-html-ai-clean-up' ), ), ) ); return; } if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) { return; } $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || ! isset( $screen->post_type ) || ! in_array( $screen->post_type, $this->get_supported_post_types(), true ) ) { return; } $post_type_object = get_post_type_object( $screen->post_type ); if ( $post_type_object && isset( $post_type_object->cap->edit_posts ) && ! current_user_can( $post_type_object->cap->edit_posts ) ) { return; } $settings = $this->get_settings(); wp_enqueue_style( 'agizohacu-admin', AGIZOHACU_URL . 'assets/css/agizohacu-admin.css', array(), AGIZOHACU_VERSION ); wp_enqueue_script( 'agizohacu-admin', AGIZOHACU_URL . 'assets/js/agizohacu-admin.js', array(), AGIZOHACU_VERSION, true ); $default_settings = $this->get_default_settings(); wp_localize_script( 'agizohacu-admin', 'agizohacuCleanHtml', array( 'nonceFieldId' => 'agizohacu_clean_html_nonce', 'modeSelectId' => 'agizohacu-clean-mode', 'scanButtonId' => 'agizohacu-scan-html-button', 'previewButtonId' => 'agizohacu-preview-html-button', 'cleanSelectedButtonId' => 'agizohacu-clean-selected-button', 'qualityButtonId' => 'agizohacu-quality-check-button', 'cleanButtonId' => 'agizohacu-clean-html-button', 'undoButtonId' => 'agizohacu-undo-html-button', 'previewBoxId' => 'agizohacu-preview-box', 'previewRiskId' => 'agizohacu-preview-risk', 'previewListId' => 'agizohacu-preview-list', 'applyPreviewId' => 'agizohacu-apply-preview-button', 'copyCleanedId' => 'agizohacu-copy-cleaned-button', 'closePreviewId' => 'agizohacu-close-preview-button', 'qualityBoxId' => 'agizohacu-quality-box', 'qualityScoreId' => 'agizohacu-quality-score', 'qualityListId' => 'agizohacu-quality-list', 'statusId' => 'agizohacu-clean-html-status', 'defaultMode' => isset( $settings['default_mode'] ) ? sanitize_key( $settings['default_mode'] ) : 'auto', 'customRules' => isset( $settings['custom_rules'] ) && is_array( $settings['custom_rules'] ) ? array_map( 'rest_sanitize_boolean', $settings['custom_rules'] ) : $default_settings['custom_rules'], 'protectedRules' => isset( $settings['protected_rules'] ) && is_array( $settings['protected_rules'] ) ? array_map( 'rest_sanitize_boolean', $settings['protected_rules'] ) : $default_settings['protected_rules'], 'messages' => array( 'missingNonce' => esc_html__( 'Security token missing. Please refresh the editor page.', 'agizoai-html-ai-clean-up' ), 'cleaned' => esc_html__( 'HTML cleaned. Please review the content before saving.', 'agizoai-html-ai-clean-up' ), 'empty' => esc_html__( 'No editor content found to clean.', 'agizoai-html-ai-clean-up' ), 'nothingFound' => esc_html__( 'No removable AI/tool markup was found with the selected preset.', 'agizoai-html-ai-clean-up' ), 'scanComplete' => esc_html__( 'AI Paste Scan Report complete. No changes were applied.', 'agizoai-html-ai-clean-up' ), 'previewReady' => esc_html__( 'Preview ready. Review the report before applying the clean-up.', 'agizoai-html-ai-clean-up' ), 'undoReady' => esc_html__( 'Previous editor content restored. Review before saving.', 'agizoai-html-ai-clean-up' ), 'copyDone' => esc_html__( 'Cleaned HTML copied to clipboard.', 'agizoai-html-ai-clean-up' ), 'copyFailed' => esc_html__( 'Clipboard copy failed. You can still apply the previewed clean-up.', 'agizoai-html-ai-clean-up' ), 'selectedUnsupported'=> esc_html__( 'No selected block was found. Select a block in the block editor first.', 'agizoai-html-ai-clean-up' ), 'selectedCleaned' => esc_html__( 'Selected block cleaned. Please review the content before saving.', 'agizoai-html-ai-clean-up' ), 'autoDetected' => esc_html__( 'Auto Detect selected preset:', 'agizoai-html-ai-clean-up' ), 'riskLabel' => esc_html__( 'Risk level:', 'agizoai-html-ai-clean-up' ), 'recommendedLabel' => esc_html__( 'Recommended preset:', 'agizoai-html-ai-clean-up' ), 'protectedLabel' => esc_html__( 'Protected items kept intact:', 'agizoai-html-ai-clean-up' ), 'qualityComplete' => esc_html__( 'HTML quality check complete. No changes were applied.', 'agizoai-html-ai-clean-up' ), 'qualityScoreLabel' => esc_html__( 'HTML quality score:', 'agizoai-html-ai-clean-up' ), 'accessibilityLabel' => esc_html__( 'Accessibility warning:', 'agizoai-html-ai-clean-up' ), 'seoSafeLabel' => esc_html__( 'SEO-safe HTML check:', 'agizoai-html-ai-clean-up' ), 'htmlQualityLabel' => esc_html__( 'HTML structure check:', 'agizoai-html-ai-clean-up' ), 'noQualityIssues' => esc_html__( 'No major accessibility, SEO-safe HTML or structure issues were detected.', 'agizoai-html-ai-clean-up' ), ), 'reportLabels' => array( 'comments' => esc_html__( 'HTML comments', 'agizoai-html-ai-clean-up' ), 'inlineSvg' => esc_html__( 'inline SVG blocks', 'agizoai-html-ai-clean-up' ), 'scriptStyleBlocks' => esc_html__( 'script, style or noscript blocks', 'agizoai-html-ai-clean-up' ), 'hiddenInputs' => esc_html__( 'hidden input fields', 'agizoai-html-ai-clean-up' ), 'hiddenElements' => esc_html__( 'hidden or invisible elements', 'agizoai-html-ai-clean-up' ), 'dataAttrs' => esc_html__( 'data-* attributes', 'agizoai-html-ai-clean-up' ), 'ariaAttrs' => esc_html__( 'aria-* attributes', 'agizoai-html-ai-clean-up' ), 'roleAttrs' => esc_html__( 'role attributes', 'agizoai-html-ai-clean-up' ), 'styleAttrs' => esc_html__( 'style attributes', 'agizoai-html-ai-clean-up' ), 'classOnDivs' => esc_html__( 'class attributes on div wrappers', 'agizoai-html-ai-clean-up' ), 'idOnDivs' => esc_html__( 'id attributes on div wrappers', 'agizoai-html-ai-clean-up' ), 'emptyAttrs' => esc_html__( 'empty attributes', 'agizoai-html-ai-clean-up' ), 'eventHandlerAttrs' => esc_html__( 'JavaScript event attributes', 'agizoai-html-ai-clean-up' ), 'xmlAttrs' => esc_html__( 'XML or namespace attributes', 'agizoai-html-ai-clean-up' ), 'msOfficeAttrs' => esc_html__( 'Microsoft Office formatting attributes/classes', 'agizoai-html-ai-clean-up' ), 'disallowedTags' => esc_html__( 'disallowed tags removed by Strict Article Clean', 'agizoai-html-ai-clean-up' ), 'disallowedAttrs' => esc_html__( 'disallowed attributes removed by Strict Article Clean', 'agizoai-html-ai-clean-up' ), ), 'modeLabels' => array_map( 'esc_html', $this->get_mode_labels() ), 'riskLabels' => array( 'clean' => esc_html__( 'Clean', 'agizoai-html-ai-clean-up' ), 'low' => esc_html__( 'Low', 'agizoai-html-ai-clean-up' ), 'medium' => esc_html__( 'Medium', 'agizoai-html-ai-clean-up' ), 'high' => esc_html__( 'High', 'agizoai-html-ai-clean-up' ), ), 'qualityLabels' => array( 'excellent' => esc_html__( 'Excellent', 'agizoai-html-ai-clean-up' ), 'good' => esc_html__( 'Good', 'agizoai-html-ai-clean-up' ), 'needs_review' => esc_html__( 'Needs review', 'agizoai-html-ai-clean-up' ), 'poor' => esc_html__( 'Poor', 'agizoai-html-ai-clean-up' ), ), 'qualityIssueLabels' => array( 'imagesMissingAlt' => esc_html__( 'Images without alt text', 'agizoai-html-ai-clean-up' ), 'imagesEmptyAlt' => esc_html__( 'Images with empty alt text', 'agizoai-html-ai-clean-up' ), 'iframesMissingTitle' => esc_html__( 'Embeds or iframes without title attributes', 'agizoai-html-ai-clean-up' ), 'emptyLinks' => esc_html__( 'Links without visible text', 'agizoai-html-ai-clean-up' ), 'weakLinkText' => esc_html__( 'Weak link text such as click here or read more', 'agizoai-html-ai-clean-up' ), 'tablesWithoutHeaders' => esc_html__( 'Tables without header cells', 'agizoai-html-ai-clean-up' ), 'duplicateIds' => esc_html__( 'Duplicate HTML id attributes', 'agizoai-html-ai-clean-up' ), 'headingLevelJumps' => esc_html__( 'Skipped heading levels', 'agizoai-html-ai-clean-up' ), 'h1InPostContent' => esc_html__( 'H1 tags inside post content', 'agizoai-html-ai-clean-up' ), 'longContentWithoutHeadings'=> esc_html__( 'Long content without subheadings', 'agizoai-html-ai-clean-up' ), 'longParagraphs' => esc_html__( 'Long paragraphs that may hurt readability', 'agizoai-html-ai-clean-up' ), 'unsafeOrEmptyLinks' => esc_html__( 'Unsafe or empty links', 'agizoai-html-ai-clean-up' ), 'emptyBlocks' => esc_html__( 'Empty paragraphs or wrapper blocks', 'agizoai-html-ai-clean-up' ), 'tooManyWrappers' => esc_html__( 'Too many div or span wrappers', 'agizoai-html-ai-clean-up' ), 'markdownArtifacts' => esc_html__( 'Markdown artefacts left in HTML', 'agizoai-html-ai-clean-up' ), 'veryShortContent' => esc_html__( 'Very short content', 'agizoai-html-ai-clean-up' ), 'parserIssue' => esc_html__( 'HTML parser issue', 'agizoai-html-ai-clean-up' ), ), ) ); } } AGIZOHACU_Plugin::instance();