api_handler ) ) { return; } $this->load_options(); // Instantiate handlers (Dependency Injection) $this->logger = new AINP_Status_Logger(); $this->api_handler = new AINP_Api_Handler( $this->options, $this->logger ); $this->image_handler = new AINP_Image_Handler( $this->options, $this->logger, $this->api_handler ); $this->processor = new AINP_Post_Processor( $this->options, $this->logger, $this->api_handler, $this->image_handler ); $this->cron_handler = new AINP_Cron_Handler( $this->options, $this->logger, $this->processor ); $this->ajax = new AINP_Ajax( $this ); $this->admin = new AINP_Admin( $this ); $this->define_hooks(); } /** * Loads the plugin options from the database, merging with defaults. */ public function load_options() { $this->options = get_option( 'ainp_settings', $this->get_default_options() ); // Ensure specific modules adhere to free limitations if somehow modified directly in DB $this->options['enable_auto_search'] = false; // Force disabled for Free compliance logic $this->options['module_force_ia_image'] = false; // Force disabled // Pass the loaded options to handler instances if they exist (refresh state). if ( isset( $this->api_handler ) ) { $this->api_handler->set_options( $this->options ); } if ( isset( $this->image_handler ) ) { $this->image_handler->set_options( $this->options ); } if ( isset( $this->processor ) ) { $this->processor->set_options( $this->options ); } if ( isset( $this->cron_handler ) ) { $this->cron_handler->set_options( $this->options ); } } /** * Defines all plugin hooks for WordPress. */ private function define_hooks() { // Shutdown function for fatal error catching register_shutdown_function( array( $this, 'handle_fatal_shutdown' ) ); // Initialization and Core WP Hooks add_action( 'init', array( $this, 'register_custom_post_status' ) ); add_filter( 'display_post_states', array( $this, 'add_display_post_states' ), 10, 2 ); // Admin Area Hooks add_action( 'admin_init', array( $this->cron_handler, 'check_and_trigger_overdue_cron_on_admin_load' ) ); add_action( 'admin_menu', array( $this->admin, 'admin_menu_page' ) ); add_action( 'admin_enqueue_scripts', array( $this->admin, 'enqueue_admin_scripts' ) ); // Registers all wp_ajax_* actions $this->ajax->register_ajax_hooks(); // Admin Columns (Post List) add_filter( 'manage_posts_columns', array( $this, 'add_image_column_head' ) ); add_action( 'manage_posts_custom_column', array( $this, 'add_image_column_content' ), 10, 2 ); // Cron Job Hooks add_action( AINP_AUTO_FETCH_CRON_HOOK, array( $this->cron_handler, 'execute_automatic_search_callback' ) ); add_action( AINP_GENERATE_IMAGE_HOOK, array( $this->image_handler, 'execute_image_generation_task' ), 10, 1 ); add_action( AINP_PROCESS_BATCH_HOOK, array( $this->cron_handler, 'execute_batch_item' ) ); // Shortcode and Content Filtering add_shortcode( 'ainp_native_publisher', array( $this, 'render_shortcode' ) ); add_filter( 'wp_kses_allowed_html', array( $this, 'allow_custom_html_tags' ), 10, 2 ); // Add custom cron schedules filter add_filter( 'cron_schedules', array( $this->cron_handler, 'add_custom_cron_schedules' ) ); } /** * Returns the default options for the plugin. * * @return array Default options. */ public function get_default_options() { return array( // General Settings 'sources_config' => array(), 'item_processing_timeout' => 180, 'respect_interval' => 4, 'api_request_delay' => 2, 'enable_feed_cache' => true, 'feed_cache_duration' => 300, 'default_post_status' => 'draft', 'default_author_id' => 1, 'target_language' => 'Portuguese (Brazil)', // API Settings 'main_ia_provider' => 'groq', // Default Free 'gemini_api_key' => '', 'gemini_model_selection' => 'gemini-1.5-flash-001', 'groq_api_key' => '', 'groq_model_selection' => 'llama-3.1-8b-instant', 'imagen_api_key' => '', 'imagen_project_id' => '', 'imagen_location' => 'us-central1', // Image Settings 'main_image_provider' => 'unsplash', // Default Free 'unsplash_access_key' => '', // Fetching Settings 'manual_search_depth_choice' => '1', 'manual_search_depth_custom' => 5, 'enable_auto_search' => false, 'auto_search_interval' => 'disabled', 'auto_search_depth' => '1', // Module Settings 'module_rewrite_content' => true, 'module_generate_title' => true, 'module_generate_category' => true, 'module_generate_tags' => true, 'module_generate_images' => true, 'module_force_ia_image' => false, 'module_add_watermark' => false, // Disabled in Free default 'module_source_credit' => true, 'module_keyword_filters' => false, // Advanced Settings 'enable_execution_timeout_guard' => true, 'delete_awaiting_image_on_completion' => true, 'image_processing_mode' => 'async', // Keyword Filters & Source Credits 'include_keywords' => '', 'exclude_keywords' => '', 'source_credit_format' => '

' . esc_html__( 'Source:', 'ainp-ai-native-publisher' ) . ' {source_name}

', // AI Prompts (Defaults) 'prompt_originality_prefix' => 'Act as an expert journalist. In addition to rewriting the following text, you must add a new layer of value, such as critical analysis, relevant historical context, or a unique perspective that goes beyond simple reporting.', 'prompt_rewrite' => "Rewrite the following news article in formal, neutral, and impartial language, with a minimum of 300 words. The rewrite should expand the text with relevant additional information that can be inferred from the topic. Structure the text with clear paragraphs, and **any subtitle must be formatted as a bold H2 heading (e.g., `

Your Subtitle Here

`)**. \n\nOriginal News: {original_text}", 'prompt_title' => "Based on the following original title and the rewritten news content, create a new catchy, concise, and SEO-optimized title. Respond only with the new title.\n\nOriginal Title: {original_title}\n\nRewritten Content: {rewritten_content}", 'prompt_category' => "Given the following news article, provide a single concise and relevant category for it, strictly based on the provided text. Choose a category from the following list: [{current_categories}]. Respond only with the category name.", 'prompt_tags' => "Given the following news article, identify the 3 to 5 most relevant keywords for SEO and create catchy, concise tags from them. Respond with a JSON array of strings.", 'prompt_imagen' => 'Create a featured image for an online news article. IT IS CRUCIAL THAT THERE IS NEVER, UNDER ANY CIRCUMSTANCES, ANY VISIBLE TEXT IN THE IMAGE. Article Title: "{title}". Content Summary: "{content_summary}".', ); } /** * Plugin activation routine. */ public function activate_plugin() { if ( ! isset( $this->cron_handler ) ) { $this->initialize_plugin(); } $this->register_custom_post_status(); flush_rewrite_rules(); if ( isset( $this->logger ) ) { $this->logger->update_processing_status( 'idle', 0, 0, __( 'Plugin activated. System is idle.', 'ainp-ai-native-publisher' ) ); } delete_option( AINP_Status_Logger::CANCEL_FLAG_OPTION ); delete_option( AINP_PENDING_IMAGE_TASKS_OPTION ); // Ensure Welcome Panel is shown delete_user_meta( get_current_user_id(), 'ainp_hide_welcome_panel' ); } /** * Plugin deactivation routine. */ public function deactivate_plugin() { wp_clear_scheduled_hook( AINP_AUTO_FETCH_CRON_HOOK ); wp_clear_scheduled_hook( AINP_GENERATE_IMAGE_HOOK ); wp_clear_scheduled_hook( AINP_PROCESS_BATCH_HOOK ); delete_transient( 'ainp_fallback_cron_check' ); delete_option( AINP_PENDING_IMAGE_TASKS_OPTION ); } /** * Handles fatal errors during execution. */ public function handle_fatal_shutdown() { if ( ! isset( $this->logger ) ) { return; } $status = $this->logger->get_processing_status(); if ( ! isset( $status['status'] ) || 'processing' !== $status['status'] ) { return; } $error = error_get_last(); if ( null !== $error && in_array( $error['type'], array( E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR, E_CORE_WARNING, E_COMPILE_WARNING, E_PARSE ), true ) ) { $error_message = sprintf( /* translators: 1: Error message, 2: File path, 3: Line number */ __( 'FATAL ERROR DETECTED: %1$s. File: %2$s, Line: %3$s.', 'ainp-ai-native-publisher' ), $error['message'], basename( $error['file'] ), $error['line'] ); $this->logger->add_log_entry( 'error', $error_message ); $this->logger->update_processing_status( 'idle', $status['current'], $status['total'], __( 'Process interrupted by a fatal error. System reset.', 'ainp-ai-native-publisher' ) ); } } /** * Registers the custom post status 'awaiting_image'. */ public function register_custom_post_status() { register_post_status( AINP_AWAITING_IMAGE_STATUS, array( 'label' => _x( 'Awaiting Image', 'post status', 'ainp-ai-native-publisher' ), 'public' => false, 'exclude_from_search' => true, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, /* translators: %s: Number of posts */ 'label_count' => _n_noop( 'Awaiting Image (%s)', 'Awaiting Image (%s)', 'ainp-ai-native-publisher' ), ) ); } public function add_display_post_states( $post_states, $post ) { if ( AINP_AWAITING_IMAGE_STATUS === $post->post_status ) { $post_states[ AINP_AWAITING_IMAGE_STATUS ] = __( 'Awaiting Image', 'ainp-ai-native-publisher' ); } return $post_states; } public function allow_custom_html_tags( $allowed_post_tags, $context ) { if ( 'post' === $context ) { $allowed_post_tags['h2']['class'] = true; $allowed_post_tags['h2']['id'] = true; $allowed_post_tags['strong']['class'] = true; $allowed_post_tags['span']['class'] = true; } return $allowed_post_tags; } /** * Adds the 'Image' column to the admin posts list. */ public function add_image_column_head( $columns ) { $new_columns = array(); foreach ( $columns as $key => $title ) { if ( 'title' === $key ) { $new_columns['ainp_image'] = ''; } $new_columns[ $key ] = $title; } return isset( $new_columns['ainp_image'] ) ? $new_columns : array_merge( $columns, array( 'ainp_image' => __( 'Image', 'ainp-ai-native-publisher' ) ) ); } public function add_image_column_content( $column_name, $post_id ) { if ( 'ainp_image' === $column_name ) { if ( has_post_thumbnail( $post_id ) ) { echo get_the_post_thumbnail( $post_id, array( 50, 50 ), array( 'class' => 'ainp-admin-thumbnail' ) ); } else { echo ''; } } } /** * Renders the shortcode [ainp_native_publisher]. */ public function render_shortcode( $atts ) { $atts = shortcode_atts( array( 'limit' => 5, 'columns' => 1, 'order' => 'DESC', 'orderby' => 'date', 'category' => '', 'tag' => '', ), $atts, 'ainp_native_publisher' ); $query_args = array( 'post_type' => 'post', 'posts_per_page' => intval( $atts['limit'] ), 'order' => sanitize_key( $atts['order'] ), 'orderby' => sanitize_key( $atts['orderby'] ), 'post_status' => 'publish', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( 'key' => '_ainp_guid', 'compare' => 'EXISTS' ), ), ); if ( ! empty( $atts['category'] ) ) { $query_args['category_name'] = sanitize_text_field( $atts['category'] ); } if ( ! empty( $atts['tag'] ) ) { $query_args['tag'] = sanitize_text_field( $atts['tag'] ); } $news_query = new WP_Query( $query_args ); $output = ''; if ( $news_query->have_posts() ) { // Enqueue public style only when shortcode is used wp_enqueue_style( 'ainp-public-style', AINP_PLUGIN_URL . 'css/public-style.css', array(), AINP_VERSION ); $cols = intval( $atts['columns'] ); if ( $cols < 1 || $cols > 4 ) { $cols = 1; } $output .= '
'; while ( $news_query->have_posts() ) { $news_query->the_post(); $output .= '
'; $output .= '
'; if ( has_post_thumbnail() ) { $output .= '
' . get_the_post_thumbnail( get_the_ID(), 'medium_large' ) . '
'; } $output .= '

' . esc_html( get_the_title() ) . '

'; $output .= '
'; $output .= ''; $output .= '
' . get_the_excerpt() . '
'; $output .= '
'; } $output .= '
'; wp_reset_postdata(); } else { $output = '

' . esc_html__( 'No aggregated news found matching the criteria.', 'ainp-ai-native-publisher' ) . '

'; } return $output; } // --- Helper methods for dashboard stats --- public function get_total_plugin_posts_count() { global $wpdb; $count = wp_cache_get( 'ainp_total_plugin_posts_count', 'ainp-ai-native-publisher' ); if ( false === $count ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = 'post' AND p.post_status IN ('publish', 'draft', 'pending', 'future', 'private', %s) AND pm.meta_key = '_ainp_guid'", AINP_AWAITING_IMAGE_STATUS ) ); wp_cache_set( 'ainp_total_plugin_posts_count', $count, 'ainp-ai-native-publisher', 10 * MINUTE_IN_SECONDS ); } return $count ? (int) $count : 0; } public function get_total_ia_generated_images_count() { global $wpdb; $count = wp_cache_get( 'ainp_total_ia_generated_images_count', 'ainp-ai-native-publisher' ); if ( false === $count ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value IN (%s, %s)", AINP_IMAGE_SOURCE_META_KEY, 'ia_generated', 'ia_generated_fallback' ) ); wp_cache_set( 'ainp_total_ia_generated_images_count', $count, 'ainp-ai-native-publisher', 10 * MINUTE_IN_SECONDS ); } return $count ? (int) $count : 0; } public function get_active_rss_feeds_count() { $sources = $this->options['sources_config'] ?? array(); return count( $sources ); } } /** * Begins execution of the plugin. */ function ainp_run() { return AINP_Native_Publisher::get_instance(); } add_action( 'plugins_loaded', 'ainp_run', 10 ); /** * Adds a "Settings" link to the plugin action links. */ function ainp_add_settings_link( $links ) { $settings_link = '' . __( 'Settings', 'ainp-ai-native-publisher' ) . ''; array_unshift( $links, $settings_link ); return $links; } add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'ainp_add_settings_link' );