403Webshell
Server IP : 54.37.205.81  /  Your IP : 216.73.216.76
Web Server : nginx/1.22.1
System : Linux vps-249481fa 6.1.0-50-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.176-1 (2026-07-02) x86_64
User : debian ( 1000)
PHP Version : 8.2.32
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /proc/self/cwd/wp-content/plugins/scriba-ai/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /proc/self/cwd/wp-content/plugins/scriba-ai/scriba-ai.php
<?php
/**
 * Plugin Name: SCRIBA – AI Auto Writer
 * Description: Agente AI che analizza più fonti di news moda e genera articoli automatici per il tuo sito WordPress.
 * Author: Marcy + OpenAI
 * Version: 1.6.0
 * Text Domain: scriba-ai
 * License: GPL v2 or later
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Opzioni
define( 'SCRIBA_AI_OPTION_API_KEY',           'scriba_ai_api_key' );
define( 'SCRIBA_AI_OPTION_MODEL',             'scriba_ai_model' );
define( 'SCRIBA_AI_OPTION_RSS_URL',           'scriba_ai_rss_url' );
define( 'SCRIBA_AI_OPTION_SITE_NICHE',        'scriba_ai_site_niche' );
define( 'SCRIBA_AI_OPTION_CATEGORY_ID',       'scriba_ai_category_id' );
define( 'SCRIBA_AI_OPTION_STATUS',            'scriba_ai_post_status' );
define( 'SCRIBA_AI_OPTION_INTERVAL_HOURS',    'scriba_ai_interval_hours' );
define( 'SCRIBA_AI_OPTION_MAX_POSTS',         'scriba_ai_max_posts' );
define( 'SCRIBA_AI_OPTION_AUTHOR_ID',         'scriba_ai_author_id' );
define( 'SCRIBA_AI_OPTION_TAGS',              'scriba_ai_tags' );
define( 'SCRIBA_AI_OPTION_ENABLE_LOGGING',    'scriba_ai_enable_logging' );
define( 'SCRIBA_AI_OPTION_ENABLE_IMAGES',     'scriba_ai_enable_images' );
define( 'SCRIBA_AI_OPTION_EXCLUDE_KEYWORDS',  'scriba_ai_exclude_keywords' );
define( 'SCRIBA_AI_OPTION_ARTICLE_STYLE',     'scriba_ai_article_style' );

// Default
define( 'SCRIBA_AI_DEFAULT_MODEL',        'gpt-4o' );
define( 'SCRIBA_AI_DEFAULT_INTERVAL',     24 );
define( 'SCRIBA_AI_DEFAULT_MAX_POSTS',    3 );
define( 'SCRIBA_AI_DEFAULT_RSS',          'https://news.google.com/rss/search?q=moda+italiana&hl=it&gl=IT&ceid=IT:it' );
define( 'SCRIBA_AI_DEFAULT_ARTICLE_STYLE','mix_ab' ); // stile E: istituzionale + magazine

if ( ! class_exists( 'SCRIBA_AI' ) ) :

class SCRIBA_AI {

    private static $instance = null;
    private $log_enabled = false;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->log_enabled = (bool) get_option( SCRIBA_AI_OPTION_ENABLE_LOGGING, false );

        add_filter( 'cron_schedules', array( $this, 'add_cron_schedule' ) );
        add_action( 'scriba_ai_cron_event', array( $this, 'run_agent' ) );
        add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
        add_action( 'admin_init', array( $this, 'register_settings' ) );

        add_action( 'wp_ajax_scriba_ai_test_connection', array( $this, 'ajax_test_connection' ) );
        add_action( 'wp_ajax_scriba_ai_get_logs', array( $this, 'ajax_get_logs' ) );

        register_activation_hook( __FILE__, array( $this, 'activate' ) );
        register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
    }

    public function activate() {
        $this->create_log_table();
        $this->schedule_cron();
    }

    public function deactivate() {
        $timestamp = wp_next_scheduled( 'scriba_ai_cron_event' );
        if ( $timestamp ) {
            wp_unschedule_event( $timestamp, 'scriba_ai_cron_event' );
        }
    }

    private function create_log_table() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'scriba_ai_logs';
        $charset_collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE {$table_name} (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            timestamp datetime DEFAULT CURRENT_TIMESTAMP,
            type varchar(50) NOT NULL,
            message text NOT NULL,
            post_id bigint(20) DEFAULT NULL,
            PRIMARY KEY  (id),
            KEY timestamp (timestamp),
            KEY type (type)
        ) {$charset_collate};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql );
    }

    public function add_cron_schedule( $schedules ) {
        $hours = (int) get_option( SCRIBA_AI_OPTION_INTERVAL_HOURS, SCRIBA_AI_DEFAULT_INTERVAL );
        $hours = max( 1, min( 168, $hours ) );

        $schedules['scriba_ai_custom'] = array(
            'interval' => $hours * HOUR_IN_SECONDS,
            'display'  => sprintf( __( 'SCRIBA AI (ogni %d ore)', 'scriba-ai' ), $hours ),
        );
        return $schedules;
    }

    private function schedule_cron() {
        if ( ! wp_next_scheduled( 'scriba_ai_cron_event' ) ) {
            wp_schedule_event( time() + 300, 'scriba_ai_custom', 'scriba_ai_cron_event' );
        }
    }

    public function run_agent() {
        $this->log( 'info', 'Avvio agente SCRIBA' );

        $api_key = trim( get_option( SCRIBA_AI_OPTION_API_KEY, '' ) );
        $rss_url = trim( get_option( SCRIBA_AI_OPTION_RSS_URL, '' ) );

        if ( empty( $api_key ) ) {
            $this->log( 'error', 'API key mancante' );
            return;
        }

        $model      = trim( get_option( SCRIBA_AI_OPTION_MODEL, SCRIBA_AI_DEFAULT_MODEL ) );
        $site_niche = trim( get_option( SCRIBA_AI_OPTION_SITE_NICHE, 'Moda italiana, stilisti emergenti, eventi fashion, Made in Italy' ) );
        $cat_id     = (int) get_option( SCRIBA_AI_OPTION_CATEGORY_ID, 0 );
        $status     = get_option( SCRIBA_AI_OPTION_STATUS, 'draft' );
        $max_posts  = (int) get_option( SCRIBA_AI_OPTION_MAX_POSTS, SCRIBA_AI_DEFAULT_MAX_POSTS );
        $author_id  = (int) get_option( SCRIBA_AI_OPTION_AUTHOR_ID, 1 );
        $tags       = get_option( SCRIBA_AI_OPTION_TAGS, '' );
        $style      = get_option( SCRIBA_AI_OPTION_ARTICLE_STYLE, SCRIBA_AI_DEFAULT_ARTICLE_STYLE );
        $exclude_raw= get_option( SCRIBA_AI_OPTION_EXCLUDE_KEYWORDS, '' );
        $enable_images = (bool) get_option( SCRIBA_AI_OPTION_ENABLE_IMAGES, false );

        $exclude_keywords = array();
        if ( ! empty( $exclude_raw ) ) {
            $parts = array_map( 'trim', explode( ',', $exclude_raw ) );
            foreach ( $parts as $p ) {
                if ( $p !== '' ) {
                    $exclude_keywords[] = strtolower( $p );
                }
            }
        }

        $posts_today = $this->count_posts_today( $status );
        if ( $posts_today >= $max_posts ) {
            $this->log( 'info', "Limite giornaliero raggiunto: {$posts_today}/{$max_posts}" );
            return;
        }

        $news_items = $this->fetch_news_items( $rss_url, $exclude_keywords );
        if ( empty( $news_items ) ) {
            $this->log( 'error', 'Nessuna notizia valida trovata (tutte escluse o feed vuoti)' );
            return;
        }

        $this->log( 'info', sprintf( 'Trovate %d notizie dal feed dopo filtro esclusioni', count( $news_items ) ) );

        $prompt  = $this->build_prompt( $news_items, $site_niche, $exclude_keywords, $style );
        $article = $this->call_openai( $api_key, $model, $prompt );

        if ( ! $article || empty( $article['title'] ) || empty( $article['content_html'] ) ) {
            $this->log( 'error', 'Risposta AI non valida o incompleta' );
            return;
        }

        $post_data = array(
            'post_title'   => wp_strip_all_tags( $article['title'] ),
            'post_content' => wp_kses_post( $article['content_html'] ),
            'post_excerpt' => isset( $article['excerpt'] ) ? wp_strip_all_tags( $article['excerpt'] ) : '',
            'post_status'  => in_array( $status, array( 'draft', 'publish' ), true ) ? $status : 'draft',
            'post_type'    => 'post',
            'post_author'  => $author_id > 0 ? $author_id : get_current_user_id(),
            'post_date'    => current_time( 'mysql' ),
        );

        if ( $cat_id > 0 ) {
            $post_data['post_category'] = array( $cat_id );
        }

        if ( ! empty( $tags ) ) {
            $tag_array = array_map( 'trim', explode( ',', $tags ) );
            $post_data['tags_input'] = array_filter( $tag_array );
        }

        $post_id = wp_insert_post( $post_data, true );

        if ( is_wp_error( $post_id ) ) {
            $this->log( 'error', 'Errore creazione post: ' . $post_id->get_error_message() );
            return;
        }

        if ( ! empty( $article['meta_description'] ) ) {
            update_post_meta( $post_id, '_yoast_wpseo_metadesc', $article['meta_description'] );
            update_post_meta( $post_id, 'rank_math_description', $article['meta_description'] );
            update_post_meta( $post_id, '_aioseop_description', $article['meta_description'] );
        }

        if ( ! empty( $article['focus_keyword'] ) ) {
            update_post_meta( $post_id, '_yoast_wpseo_focuskw', $article['focus_keyword'] );
            update_post_meta( $post_id, 'rank_math_focus_keyword', $article['focus_keyword'] );
        }

        update_post_meta( $post_id, '_scriba_ai_generated', '1' );
        update_post_meta( $post_id, '_scriba_ai_generated_date', current_time( 'mysql' ) );
        // Prima prova con immagine dal feed
        $this->maybe_set_featured_image_from_news( $post_id, $news_items );

        // Se non c'è immagine e le immagini automatiche sono abilitate, prova con OpenAI
        if ( $enable_images && ! has_post_thumbnail( $post_id ) ) {
            $this->maybe_generate_ai_featured_image( $post_id, $article, $site_niche );
        }

        $this->log( 'success', sprintf( 'Articolo creato con successo (ID: %d)', $post_id ), $post_id );

        return $post_id;
    }

    private function count_posts_today( $status ) {
        $today = getdate();
        $args = array(
            'post_type'      => 'post',
            'post_status'    => $status,
            'date_query'     => array(
                array(
                    'year'  => $today['year'],
                    'month' => $today['mon'],
                    'day'   => $today['mday'],
                ),
            ),
            'meta_query'     => array(
                array(
                    'key'   => '_scriba_ai_generated',
                    'value' => '1',
                ),
            ),
            'posts_per_page' => -1,
            'fields'         => 'ids',
        );
        $q = new WP_Query( $args );
        return (int) $q->found_posts;
    }

    private function fetch_news_items( $rss_url, $exclude_keywords = array() ) {
        $sources = array();

        if ( ! empty( $rss_url ) ) {
            $sources[] = $rss_url;
        } else {
            // feed multipli specifici per moda italiana
            $sources[] = SCRIBA_AI_DEFAULT_RSS; // Google News moda italiana
            $sources[] = 'https://it.fashionnetwork.com/rss';
            $sources[] = 'https://www.ansa.it/sito/notizie/magazine/moda/moda_rss.xml';
        }

        $items = array();
        $seen  = array();
        $max_total = 30;

        foreach ( $sources as $src ) {
            $more = $this->fetch_rss_items_single( $src, $exclude_keywords, $max_total - count( $items ) );
            foreach ( $more as $it ) {
                $key = md5( strtolower( $it['title'] . '|' . $it['link'] ) );
                if ( isset( $seen[ $key ] ) ) {
                    continue;
                }
                $seen[ $key ] = true;
                $items[] = $it;
                if ( count( $items ) >= $max_total ) {
                    break 2;
                }
            }
        }

        if ( empty( $items ) && ! empty( $exclude_keywords ) ) {
            $this->log( 'info', 'Tutte le notizie sono state escluse dalle parole vietate' );
        }

        return $items;
    }

    private function fetch_rss_items_single( $rss_url, $exclude_keywords = array(), $max_items = 10 ) {
        $response = wp_remote_get(
            $rss_url,
            array(
                'timeout'    => 30,
                'user-agent' => 'Mozilla/5.0 (compatible; SCRIBA AI; +' . home_url() . ')',
            )
        );

        if ( is_wp_error( $response ) ) {
            $this->log( 'error', 'Errore RSS (' . esc_url_raw( $rss_url ) . '): ' . $response->get_error_message() );
            return array();
        }

        $code = wp_remote_retrieve_response_code( $response );
        if ( 200 !== $code ) {
            $this->log( 'error', 'RSS ' . esc_url_raw( $rss_url ) . ' risponde con codice ' . $code );
            return array();
        }

        $body = wp_remote_retrieve_body( $response );
        if ( empty( $body ) ) {
            return array();
        }

        libxml_use_internal_errors( true );
        $xml = simplexml_load_string( $body );
        if ( ! $xml ) {
            $errors = libxml_get_errors();
            libxml_clear_errors();
            if ( ! empty( $errors ) ) {
                $this->log( 'error', 'Errore parsing RSS: ' . $errors[0]->message );
            } else {
                $this->log( 'error', 'Errore parsing RSS (sorgente: ' . esc_url_raw( $rss_url ) . ')' );
            }
            return array();
        }

        $items = array();
        $count = 0;

        $should_exclude = function( $title, $description ) use ( $exclude_keywords ) {
            if ( empty( $exclude_keywords ) ) {
                return false;
            }
            $haystack = strtolower( $title . ' ' . $description );
            foreach ( $exclude_keywords as $word ) {
                if ( $word !== '' && strpos( $haystack, $word ) !== false ) {
                    return true;
                }
            }
            return false;
        };

        if ( isset( $xml->channel->item ) ) {
            foreach ( $xml->channel->item as $item ) {
                $title = trim( (string) $item->title );
                $desc  = isset( $item->description ) ? trim( wp_strip_all_tags( (string) $item->description ) ) : '';
                if ( $should_exclude( $title, $desc ) ) {
                    continue;
                }
                // tenta di estrarre una immagine dal feed (enclosure, media:content, ecc.)
                $image_url = '';
                if ( isset( $item->enclosure['url'] ) ) {
                    $image_url = trim( (string) $item->enclosure['url'] );
                } elseif ( isset( $item->children('media', true)->content ) && isset( $item->children('media', true)->content[0]['url'] ) ) {
                    $image_url = trim( (string) $item->children('media', true)->content[0]['url'] );
                }

                $items[] = array(
                    'title'       => $title,
                    'description' => $desc,
                    'link'        => isset( $item->link ) ? trim( (string) $item->link ) : '',
                    'pubDate'     => isset( $item->pubDate ) ? (string) $item->pubDate : '',
                    'image'       => $image_url,
                );
                $count++;
                if ( $count >= $max_items ) {
                    break;
                }
            }
        } elseif ( isset( $xml->entry ) ) {
            foreach ( $xml->entry as $entry ) {
                $title = trim( (string) $entry->title );
                $desc  = isset( $entry->summary ) ? trim( wp_strip_all_tags( (string) $entry->summary ) ) : '';
                if ( $should_exclude( $title, $desc ) ) {
                    continue;
                }
                // tenta di estrarre immagine anche per feed Atom (se presente come media:content)
                $image_url = '';
                if ( isset( $entry->children('media', true)->content ) && isset( $entry->children('media', true)->content[0]['url'] ) ) {
                    $image_url = trim( (string) $entry->children('media', true)->content[0]['url'] );
                }

                $items[] = array(
                    'title'       => $title,
                    'description' => $desc,
                    'link'        => isset( $entry->link['href'] ) ? trim( (string) $entry->link['href'] ) : '',
                    'pubDate'     => isset( $entry->published ) ? (string) $entry->published : '',
                    'image'       => $image_url,
                );
                $count++;
                if ( $count >= $max_items ) {
                    break;
                }
            }
        }

        return $items;
    }

    /**
     * Imposta immagine in evidenza da elenco news (prima con image valida)
     */
    private function maybe_set_featured_image_from_news( $post_id, $news_items ) {
        if ( empty( $post_id ) || empty( $news_items ) ) {
            return;
        }

        if ( ! function_exists( 'media_sideload_image' ) ) {
            require_once ABSPATH . 'wp-admin/includes/image.php';
            require_once ABSPATH . 'wp-admin/includes/file.php';
            require_once ABSPATH . 'wp-admin/includes/media.php';
        }

        foreach ( $news_items as $item ) {
            if ( empty( $item['image'] ) ) {
                continue;
            }

            $image_url = esc_url_raw( $item['image'] );
            if ( empty( $image_url ) ) {
                continue;
            }

            try {
                $attachment_id = media_sideload_image( $image_url, $post_id, null, 'id' );
            } catch ( Exception $e ) {
                $this->log( 'error', 'Errore media_sideload_image: ' . $e->getMessage(), $post_id );
                $attachment_id = 0;
            }

            if ( is_wp_error( $attachment_id ) || ! $attachment_id ) {
                $this->log( 'error', 'Impossibile allegare immagine dal feed: ' . $image_url, $post_id );
                continue;
            }

            set_post_thumbnail( $post_id, $attachment_id );
            $this->log( 'info', 'Immagine in evidenza impostata da feed: ' . $image_url, $post_id );
            return;
        }
    }

    /**
     * Genera una immagine di copertina con OpenAI Images (DALL·E) e la imposta come featured image.
     */
    private function maybe_generate_ai_featured_image( $post_id, $article, $site_niche ) {
        if ( empty( $post_id ) || ! is_array( $article ) ) {
            return;
        }

        $api_key = trim( get_option( SCRIBA_AI_OPTION_API_KEY, '' ) );
        if ( empty( $api_key ) ) {
            return;
        }

        if ( ! function_exists( 'media_sideload_image' ) ) {
            require_once ABSPATH . 'wp-admin/includes/image.php';
            require_once ABSPATH . 'wp-admin/includes/file.php';
            require_once ABSPATH . 'wp-admin/includes/media.php';
        }

        $title        = isset( $article['title'] ) ? $article['title'] : '';
        $focus_keyword= isset( $article['focus_keyword'] ) ? $article['focus_keyword'] : '';
        $niche        = ! empty( $site_niche ) ? $site_niche : 'moda italiana';

        $prompt = 'Editorial fashion photography, high-end Italian runway, Vogue Italia style, dramatic lighting, refined tailoring, models on catwalk in Milan, elegant fabrics, Italian fashion design. Contesto: ' . $niche;
        if ( ! empty( $title ) ) {
            $prompt .= ' Titolo articolo: ' . $title . '.';
        }
        if ( ! empty( $focus_keyword ) ) {
            $prompt .= ' Focus keyword: ' . $focus_keyword . '.';
        }

        $endpoint = 'https://api.openai.com/v1/images/generations';

        $body = array(
            'model' => 'dall-e-3',
            'prompt' => $prompt,
            'n' => 1,
            'size' => '1024x1792',
        );

        $response = wp_remote_post(
            $endpoint,
            array(
                'headers' => array(
                    'Authorization' => 'Bearer ' . $api_key,
                    'Content-Type'  => 'application/json',
                ),
                'body'    => wp_json_encode( $body ),
                'timeout' => 120,
            )
        );

        if ( is_wp_error( $response ) ) {
            $this->log( 'error', 'Errore generazione immagine OpenAI: ' . $response->get_error_message(), $post_id );
            return;
        }

        $code = wp_remote_retrieve_response_code( $response );
        $raw  = wp_remote_retrieve_body( $response );

        if ( 200 !== $code ) {
            $this->log( 'error', 'OpenAI Images ha risposto con codice ' . $code . ': ' . substr( $raw, 0, 200 ), $post_id );
            return;
        }

        $data = json_decode( $raw, true );
        if ( empty( $data['data'][0]['url'] ) ) {
            $this->log( 'error', 'Risposta OpenAI Images senza URL immagine valido.', $post_id );
            return;
        }

        $image_url = esc_url_raw( $data['data'][0]['url'] );
        if ( empty( $image_url ) ) {
            return;
        }

        try {
            $attachment_id = media_sideload_image( $image_url, $post_id, null, 'id' );
        } catch ( Exception $e ) {
            $this->log( 'error', 'Errore media_sideload_image (AI image): ' . $e->getMessage(), $post_id );
            $attachment_id = 0;
        }

        if ( is_wp_error( $attachment_id ) || ! $attachment_id ) {
            $this->log( 'error', 'Impossibile allegare immagine AI come featured image.', $post_id );
            return;
        }

        set_post_thumbnail( $post_id, $attachment_id );
        $this->log( 'info', 'Immagine in evidenza generata con OpenAI e impostata sul post.', $post_id );
    }



        private function build_prompt( $news_items, $site_niche, $exclude_keywords, $style ) {
        $list = '';
        foreach ( $news_items as $idx => $item ) {
            $n    = $idx + 1;
            $list .= $n . '. ' . $item['title'] . "\n";
            if ( ! empty( $item['description'] ) ) {
                $desc  = substr( $item['description'], 0, 220 );
                $list .= '   ' . $desc . ( strlen( $item['description'] ) > 220 ? '...' : '' ) . "\n";
            }
        }

        $niche = ! empty( $site_niche ) ? $site_niche : 'pubblico generale';
        $date  = date_i18n( 'j F Y' );

        $exclude_text = '';
        if ( ! empty( $exclude_keywords ) ) {
            $exclude_text  = "Devi ignorare tutte le notizie che parlano in modo centrale di questi argomenti (non scrivere articoli su di essi): ";
            $exclude_text .= $exclude_keywords . ". Se una notizia contiene questi temi solo di sfondo, puoi citarla ma senza farla diventare il centro del pezzo.";
        }

        switch ( $style ) {
            case 'istituzionale':
                $style_text = "Tono istituzionale, come comunicato di una Camera della Moda regionale: chiaro, autorevole, con un 'noi' che rappresenta il sistema moda del territorio.";
                break;
            case 'magazine':
                $style_text = "Tono da magazine di moda autorevole (tipo Vogue Italia o Fashion Network): narrativo ma preciso, con analisi critica e attenzione ai nomi e ai fatti.";
                break;
            case 'mix_ab':
            default:
                $style_text = "Tono misto: istituzionale + magazine. Sei la voce ufficiale ma anche l'osservatorio critico della Camera Regionale della Moda Italiana.";
                break;
        }

        $prompt  = "Sei SCRIBA, giornalista di moda italiano e copywriter senior.\n";
        $prompt .= "Scrivi per la Camera Regionale della Moda Italiana o una realtà molto simile.\n";
        $prompt .= "Parli a un pubblico di addetti ai lavori (designer, buyer, scuole di moda, istituzioni).\n\n";

        $prompt .= "Oggi è il {$date}. Ho raccolto queste notizie recenti legate alla moda (titoli + brevi descrizioni):\n\n";
        $prompt .= $list . "\n";

        $prompt .= "Il tuo compito non è fare un riassunto generico sulla moda italiana.\n";
        $prompt .= "Devi scegliere UNA notizia principale dall'elenco, la più calda e rilevante oggi, e usarla come fulcro dell'articolo.\n";
        $prompt .= "Puoi usare al massimo 1-2 notizie di supporto per dare contesto.\n\n";

        $prompt .= "Contesto del sito: {$niche}.\n";
        if ( $exclude_text ) {
            $prompt .= $exclude_text . "\n";
        }
        $prompt .= "Stile richiesto: {$style_text}\n\n";

        $prompt .= "Linee guida contenuto (molto importanti):\n";
        $prompt .= "- Apri con un lead forte e concreto sulla notizia principale (cosa è successo, dove, chi è coinvolto).\n";
        $prompt .= "- Cita SEMPRE nomi specifici (stilisti, maison, brand, eventi, città, istituzioni) presi dalle notizie.\n";
        $prompt .= "- Se nel feed mancano nomi, puoi attingere alla tua conoscenza della moda italiana contemporanea (es. Magliano, Sunnei, Marco Rambaldi, Federico Cina, Act N°1, Del Core, MSGM, ecc.) ma usali solo se coerenti con il tema che hai scelto.\n";
        $prompt .= "- Inserisci almeno 3–5 riferimenti molto concreti (nomi, collezioni, progetti, iniziative, partnership, dati, quote).\n";
        $prompt .= "- Evita frasi vuote e stereotipate tipo 'la moda italiana è da sempre sinonimo di eccellenza' se non aggiungono nulla.\n";
        $prompt .= "- Evita panoramiche generiche sul 'futuro della moda italiana' se non sono legate a fatti o dati specifici.\n";
        $prompt .= "- Non trasformare l'articolo in un elenco di slogan: deve sembrare un pezzo da osservatorio moda serio.\n";
        $prompt .= "- Evita gossip, cronaca nera e argomenti lontani dalla moda italiana.\n";
        $prompt .= "- Non ripetere ossessivamente le espressioni 'moda italiana' o 'Made in Italy' (massimo 3 volte ciascuna in tutto il testo).\n\n";

        $prompt .= "Struttura dell'articolo:\n";
        $prompt .= "1) Titolo SEO forte centrato sulla notizia principale (non generico).\n";
        $prompt .= "2) Apertura editoriale di 2–3 paragrafi che racconta il fatto principale e spiega perché è importante per il sistema moda italiano.\n";
        $prompt .= "3) 3–5 sezioni con sottotitoli H2, ognuna con un focus chiaro (esempi: impatto sui giovani designer, ricadute sul territorio, sostenibilità, ruolo delle istituzioni, prospettive di mercato).\n";
        $prompt .= "4) Una sezione di lettura critica (cosa funziona, cosa manca, quali rischi/opportunità).\n";
        $prompt .= "5) Conclusione che richiama il ruolo di una Camera della Moda regionale: supporto ai talenti, filiera, formazione, dialogo con le istituzioni.\n\n";

        $prompt .= "SEO e output JSON:\n";
        $prompt .= "- Scegli UNA focus keyword principale legata alla notizia principale (evento, designer, progetto, tema).\n";
        $prompt .= "- Usa la focus keyword nel titolo, nell'introduzione e in almeno 2 sottotitoli H2.\n";
        $prompt .= "- Inserisci varianti e parole chiave correlate in modo naturale, senza keyword stuffing.\n";
        $prompt .= "- Crea una meta description coinvolgente (max 160 caratteri), chiara e informativa.\n";
        $prompt .= "- Crea un excerpt/sommario breve (max 200 caratteri) con taglio giornalistico.\n\n";

        $prompt .= "IMPORTANTE: Rispondi SOLO con un JSON valido, senza altri testi, nel seguente formato (usa doppi apici nel JSON finale):\n";
        $prompt .= "{\n";
        $prompt .= "  'title': 'titolo SEO dell'articolo',\n";
        $prompt .= "  'content_html': 'contenuto in HTML con <p>, <h2>, <ul>, <li>, <strong>, <em>',\n";
        $prompt .= "  'excerpt': 'sommario breve (max 200 caratteri)',\n";
        $prompt .= "  'meta_description': 'meta description SEO (max 160 caratteri)',\n";
        $prompt .= "  'focus_keyword': 'parola chiave principale'\n";
        $prompt .= "}\n";

        return $prompt;
    }


    private function call_openai( $api_key, $model, $prompt ) {
        $endpoint = 'https://api.openai.com/v1/chat/completions';

        $body = array(
            'model'       => $model,
            'messages'    => array(
                array(
                    'role'    => 'system',
                    'content' => 'Sei SCRIBA, un giornalista e copywriter di moda italiana. Rispondi sempre in italiano con JSON valido.'
                ),
                array(
                    'role'    => 'user',
                    'content' => $prompt,
                ),
            ),
            'temperature' => 0.7,
            'max_tokens'  => 3200,
            'top_p'       => 0.9,
        );

        $response = wp_remote_post(
            $endpoint,
            array(
                'headers' => array(
                    'Authorization' => 'Bearer ' . $api_key,
                    'Content-Type'  => 'application/json',
                ),
                'body'    => wp_json_encode( $body ),
                'timeout' => 120,
            )
        );

        if ( is_wp_error( $response ) ) {
            $this->log( 'error', 'Errore chiamata OpenAI: ' . $response->get_error_message() );
            return null;
        }

        $raw_body = wp_remote_retrieve_body( $response );
        $code     = wp_remote_retrieve_response_code( $response );

        if ( 200 !== $code ) {
            $this->log( 'error', "OpenAI risponde con codice {$code}: " . substr( $raw_body, 0, 300 ) );
            return null;
        }

        $data = json_decode( $raw_body, true );
        if ( ! isset( $data['choices'][0]['message']['content'] ) ) {
            $this->log( 'error', 'Formato risposta OpenAI non valido' );
            return null;
        }

        $content = trim( $data['choices'][0]['message']['content'] );
        $content = preg_replace( '/^```json\s*|```$/i', '', $content );

        $article = json_decode( $content, true );
        if ( ! is_array( $article ) ) {
            $this->log( 'error', 'Impossibile decodificare JSON dalla risposta' );
            $this->log( 'debug', 'Risposta raw: ' . substr( $content, 0, 500 ) );
            return null;
        }

        return array(
            'title'            => isset( $article['title'] ) ? sanitize_text_field( $article['title'] ) : '',
            'content_html'     => isset( $article['content_html'] ) ? wp_kses_post( $article['content_html'] ) : '',
            'excerpt'          => isset( $article['excerpt'] ) ? sanitize_textarea_field( $article['excerpt'] ) : '',
            'meta_description' => isset( $article['meta_description'] ) ? sanitize_text_field( $article['meta_description'] ) : '',
            'focus_keyword'    => isset( $article['focus_keyword'] ) ? sanitize_text_field( $article['focus_keyword'] ) : '',
        );
    }

    private function log( $type, $message, $post_id = null ) {
        if ( ! $this->log_enabled && 'error' !== $type ) {
            return;
        }

        global $wpdb;
        $table_name = $wpdb->prefix . 'scriba_ai_logs';

        $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) );
        if ( $table_exists !== $table_name ) {
            if ( 'error' === $type ) {
                error_log( '[SCRIBA AI] ' . $message );
            }
            return;
        }

        $wpdb->insert(
            $table_name,
            array(
                'timestamp' => current_time( 'mysql' ),
                'type'      => $type,
                'message'   => $message,
                'post_id'   => $post_id,
            ),
            array( '%s', '%s', '%s', '%d' )
        );

        if ( 'error' === $type ) {
            error_log( '[SCRIBA AI] ' . $message );
        }
    }

    public function add_admin_menu() {
        add_menu_page(
            __( 'SCRIBA AI', 'scriba-ai' ),
            __( 'SCRIBA AI', 'scriba-ai' ),
            'manage_options',
            'scriba-ai',
            array( $this, 'render_settings_page' ),
            'dashicons-edit',
            25
        );
    }

    public function register_settings() {
        $settings = array(
            SCRIBA_AI_OPTION_API_KEY,
            SCRIBA_AI_OPTION_MODEL,
            SCRIBA_AI_OPTION_RSS_URL,
            SCRIBA_AI_OPTION_SITE_NICHE,
            SCRIBA_AI_OPTION_CATEGORY_ID,
            SCRIBA_AI_OPTION_STATUS,
            SCRIBA_AI_OPTION_INTERVAL_HOURS,
            SCRIBA_AI_OPTION_MAX_POSTS,
            SCRIBA_AI_OPTION_AUTHOR_ID,
            SCRIBA_AI_OPTION_TAGS,
            SCRIBA_AI_OPTION_ENABLE_LOGGING,
            SCRIBA_AI_OPTION_ENABLE_IMAGES,
            SCRIBA_AI_OPTION_EXCLUDE_KEYWORDS,
            SCRIBA_AI_OPTION_ARTICLE_STYLE,
        );
        foreach ( $settings as $setting ) {
            register_setting( 'scriba_ai_options_group', $setting );
        }
    }

    public function ajax_test_connection() {
        check_ajax_referer( 'scriba_ai_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( 'Unauthorized' );
        }

        $api_key = get_option( SCRIBA_AI_OPTION_API_KEY, '' );
        if ( empty( $api_key ) ) {
            wp_send_json_error( 'API Key non configurata' );
        }

        $response = wp_remote_get(
            'https://api.openai.com/v1/models',
            array(
                'headers' => array(
                    'Authorization' => 'Bearer ' . $api_key,
                ),
                'timeout' => 30,
            )
        );

        if ( is_wp_error( $response ) ) {
            wp_send_json_error( 'Errore di connessione: ' . $response->get_error_message() );
        }

        $code = wp_remote_retrieve_response_code( $response );
        if ( 200 === $code ) {
            wp_send_json_success( 'Connessione API riuscita!' );
        } else {
            $body    = json_decode( wp_remote_retrieve_body( $response ), true );
            $message = isset( $body['error']['message'] ) ? $body['error']['message'] : 'Errore sconosciuto';
            wp_send_json_error( "Errore API ({$code}): {$message}" );
        }
    }

    public function ajax_get_logs() {
        check_ajax_referer( 'scriba_ai_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( 'Unauthorized' );
        }

        global $wpdb;
        $table_name = $wpdb->prefix . 'scriba_ai_logs';
        $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) );
        if ( $table_exists !== $table_name ) {
            wp_send_json_success( array() );
        }

        $logs = $wpdb->get_results( "SELECT * FROM {$table_name} ORDER BY timestamp DESC LIMIT 50" );
        wp_send_json_success( $logs );
    }

    public function render_settings_page() {
        if ( ! current_user_can( 'manage_options' ) ) {
            return;
        }

        if ( isset( $_POST['scriba_ai_generate_now'] ) && check_admin_referer( 'scriba_ai_generate_now_action' ) ) {
            $post_id = $this->run_agent();
            if ( $post_id ) {
                $edit_link = get_edit_post_link( $post_id );
                echo '<div class="notice notice-success is-dismissible"><p>';
                printf(
                    __( 'Articolo generato con successo! <a href="%s">Modifica articolo</a>', 'scriba-ai' ),
                    esc_url( $edit_link )
                );
                echo '</p></div>';
            } else {
                echo '<div class="notice notice-error is-dismissible"><p>' .
                    __( 'Errore durante la generazione. Controlla i log per maggiori dettagli.', 'scriba-ai' ) .
                    '</p></div>';
            }
        }

        $api_key    = get_option( SCRIBA_AI_OPTION_API_KEY, '' );
        $model      = get_option( SCRIBA_AI_OPTION_MODEL, SCRIBA_AI_DEFAULT_MODEL );
        $rss_url    = get_option( SCRIBA_AI_OPTION_RSS_URL, '' );
        $site_niche = get_option( SCRIBA_AI_OPTION_SITE_NICHE, 'Moda italiana, stilisti emergenti, eventi fashion, Made in Italy' );
        $cat_id     = get_option( SCRIBA_AI_OPTION_CATEGORY_ID, '' );
        $status     = get_option( SCRIBA_AI_OPTION_STATUS, 'draft' );
        $interval   = get_option( SCRIBA_AI_OPTION_INTERVAL_HOURS, SCRIBA_AI_DEFAULT_INTERVAL );
        $max_posts  = get_option( SCRIBA_AI_OPTION_MAX_POSTS, SCRIBA_AI_DEFAULT_MAX_POSTS );
        $author_id  = get_option( SCRIBA_AI_OPTION_AUTHOR_ID, 1 );
        $tags       = get_option( SCRIBA_AI_OPTION_TAGS, '' );
        $log_enabled= (bool) get_option( SCRIBA_AI_OPTION_ENABLE_LOGGING, false );
        $enable_images = (bool) get_option( SCRIBA_AI_OPTION_ENABLE_IMAGES, false );
        $exclude_raw= get_option( SCRIBA_AI_OPTION_EXCLUDE_KEYWORDS, '' );
        $style      = get_option( SCRIBA_AI_OPTION_ARTICLE_STYLE, SCRIBA_AI_DEFAULT_ARTICLE_STYLE );

        $authors = get_users(
            array(
                'capability' => array( 'edit_posts' ),
                'orderby'    => 'display_name',
            )
        );
        $categories = get_categories( array( 'hide_empty' => false ) );
        ?>
        <div class="wrap">
            <h1><?php esc_html_e( 'SCRIBA – Agente AI per articoli automatici', 'scriba-ai' ); ?></h1>

            <div class="scriba-ai-dashboard" style="display:flex;gap:20px;margin-top:20px;">
                <div class="scriba-ai-main" style="flex:2;min-width:0;">
                    <form method="post" action="options.php">
                        <?php settings_fields( 'scriba_ai_options_group' ); ?>

                        <div class="scriba-ai-section" style="background:#fff;padding:20px;margin-bottom:20px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);">
                            <h2><?php esc_html_e( 'Configurazione OpenAI', 'scriba-ai' ); ?></h2>
                            <table class="form-table">
                                <tr>
                                    <th scope="row"><label for="scriba_ai_api_key"><?php esc_html_e( 'OpenAI API Key', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <input type="password" id="scriba_ai_api_key" name="<?php echo esc_attr( SCRIBA_AI_OPTION_API_KEY ); ?>" value="<?php echo esc_attr( $api_key ); ?>" class="regular-text" />
                                        <button type="button" class="button" id="scriba-ai-test-connection"><?php esc_html_e( 'Test Connessione', 'scriba-ai' ); ?></button>
                                        <p class="description"><?php esc_html_e( 'La tua chiave API OpenAI.', 'scriba-ai' ); ?></p>
                                        <input type="hidden" id="scriba_ai_admin_nonce" value="<?php echo esc_attr( wp_create_nonce( 'scriba_ai_admin_nonce' ) ); ?>" />
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_model"><?php esc_html_e( 'Modello OpenAI', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <select id="scriba_ai_model" name="<?php echo esc_attr( SCRIBA_AI_OPTION_MODEL ); ?>">
                                            <option value="gpt-4o" <?php selected( $model, 'gpt-4o' ); ?>>gpt-4o (consigliato)</option>
                                            <option value="gpt-4.1" <?php selected( $model, 'gpt-4.1' ); ?>>gpt-4.1</option>
                                            <option value="gpt-4o-mini" <?php selected( $model, 'gpt-4o-mini' ); ?>>gpt-4o-mini</option>
                                            <option value="gpt-3.5-turbo" <?php selected( $model, 'gpt-3.5-turbo' ); ?>>gpt-3.5-turbo (solo per test)</option>
                                            <option value="<?php echo esc_attr( $model ); ?>" <?php selected( $model, $model ); ?>><?php echo esc_html( $model ); ?></option>
                                        </select>
                                        <p class="description"><?php esc_html_e( 'Scegli il modello in base a qualità/costo. Per articoli moda seri usa gpt-4o o 4.1.', 'scriba-ai' ); ?></p>
                                    </td>
                                </tr>
                            </table>
                        </div>

                        <div class="scriba-ai-section" style="background:#fff;padding:20px;margin-bottom:20px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);">
                            <h2><?php esc_html_e( 'Fonti e contenuti', 'scriba-ai' ); ?></h2>
                            <table class="form-table">
                                <tr>
                                    <th scope="row"><label for="scriba_ai_rss_url"><?php esc_html_e( 'Feed RSS personalizzato (opzionale)', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <input type="url" id="scriba_ai_rss_url" name="<?php echo esc_attr( SCRIBA_AI_OPTION_RSS_URL ); ?>" value="<?php echo esc_attr( $rss_url ); ?>" class="regular-text" />
                                        <p class="description">
                                            <?php esc_html_e( 'Se vuoto, SCRIBA userà automaticamente più fonti di moda (Google News, FashionNetwork, ANSA Moda).', 'scriba-ai' ); ?>
                                        </p>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_site_niche"><?php esc_html_e( 'Niche del sito', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <input type="text" id="scriba_ai_site_niche" name="<?php echo esc_attr( SCRIBA_AI_OPTION_SITE_NICHE ); ?>" value="<?php echo esc_attr( $site_niche ); ?>" class="regular-text" />
                                        <p class="description"><?php esc_html_e( 'Es. Moda italiana, stilisti emergenti, eventi fashion, Made in Italy.', 'scriba-ai' ); ?></p>
                                    </td>
                                </tr>
                            </table>
                        </div>

                        <div class="scriba-ai-section" style="background:#fff;padding:20px;margin-bottom:20px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);">
                            <h2><?php esc_html_e( 'Stile dell\'articolo', 'scriba-ai' ); ?></h2>
                            <table class="form-table">
                                <tr>
                                    <th scope="row"><?php esc_html_e( 'Seleziona stile', 'scriba-ai' ); ?></th>
                                    <td>
                                        <fieldset>
                                            <label><input type="radio" name="<?php echo esc_attr( SCRIBA_AI_OPTION_ARTICLE_STYLE ); ?>" value="news_istituzionale" <?php checked( $style, 'news_istituzionale' ); ?> /> <?php esc_html_e( 'A) News istituzionale (ufficio stampa)', 'scriba-ai' ); ?></label><br/>
                                            <label><input type="radio" name="<?php echo esc_attr( SCRIBA_AI_OPTION_ARTICLE_STYLE ); ?>" value="magazine" <?php checked( $style, 'magazine' ); ?> /> <?php esc_html_e( 'B) Magazine fashion', 'scriba-ai' ); ?></label><br/>
                                            <label><input type="radio" name="<?php echo esc_attr( SCRIBA_AI_OPTION_ARTICLE_STYLE ); ?>" value="business" <?php checked( $style, 'business' ); ?> /> <?php esc_html_e( 'C) Business / analisi mercato', 'scriba-ai' ); ?></label><br/>
                                            <label><input type="radio" name="<?php echo esc_attr( SCRIBA_AI_OPTION_ARTICLE_STYLE ); ?>" value="seo_longform" <?php checked( $style, 'seo_longform' ); ?> /> <?php esc_html_e( 'D) SEO long-form', 'scriba-ai' ); ?></label><br/>
                                            <label><input type="radio" name="<?php echo esc_attr( SCRIBA_AI_OPTION_ARTICLE_STYLE ); ?>" value="mix_ab" <?php checked( $style, 'mix_ab' ); ?> /> <?php esc_html_e( 'E) MIX A+B (istituzionale + magazine) – consigliato per Camera Moda', 'scriba-ai' ); ?></label>
                                        </fieldset>
                                    </td>
                                </tr>
                            </table>
                        </div>

                        <div class="scriba-ai-section" style="background:#fff;padding:20px;margin-bottom:20px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);">
                            <h2><?php esc_html_e( 'Impostazioni pubblicazione', 'scriba-ai' ); ?></h2>
                            <table class="form-table">
                                <tr>
                                    <th scope="row"><label for="scriba_ai_category_id"><?php esc_html_e( 'Categoria', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <select id="scriba_ai_category_id" name="<?php echo esc_attr( SCRIBA_AI_OPTION_CATEGORY_ID ); ?>">
                                            <option value=""><?php esc_html_e( 'Nessuna categoria', 'scriba-ai' ); ?></option>
                                            <?php foreach ( $categories as $cat ) : ?>
                                                <option value="<?php echo esc_attr( $cat->term_id ); ?>" <?php selected( $cat_id, $cat->term_id ); ?>><?php echo esc_html( $cat->name ); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_tags"><?php esc_html_e( 'Tags', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <input type="text" id="scriba_ai_tags" name="<?php echo esc_attr( SCRIBA_AI_OPTION_TAGS ); ?>" value="<?php echo esc_attr( $tags ); ?>" class="regular-text" />
                                        <p class="description"><?php esc_html_e( 'Tags separati da virgola.', 'scriba-ai' ); ?></p>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_exclude_keywords"><?php esc_html_e( 'Parole da ESCLUDERE', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <input type="text" id="scriba_ai_exclude_keywords" name="<?php echo esc_attr( SCRIBA_AI_OPTION_EXCLUDE_KEYWORDS ); ?>" value="<?php echo esc_attr( $exclude_raw ); ?>" class="regular-text" />
                                        <p class="description"><?php esc_html_e( 'Lista di parole (separate da virgola). Le notizie che le contengono verranno scartate.', 'scriba-ai' ); ?></p>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_author_id"><?php esc_html_e( 'Autore', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <select id="scriba_ai_author_id" name="<?php echo esc_attr( SCRIBA_AI_OPTION_AUTHOR_ID ); ?>">
                                            <?php foreach ( $authors as $author ) : ?>
                                                <option value="<?php echo esc_attr( $author->ID ); ?>" <?php selected( $author_id, $author->ID ); ?>><?php echo esc_html( $author->display_name ); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_post_status"><?php esc_html_e( 'Stato del post', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <select id="scriba_ai_post_status" name="<?php echo esc_attr( SCRIBA_AI_OPTION_STATUS ); ?>">
                                            <option value="draft" <?php selected( $status, 'draft' ); ?>><?php esc_html_e( 'Bozza (consigliato)', 'scriba-ai' ); ?></option>
                                            <option value="publish" <?php selected( $status, 'publish' ); ?>><?php esc_html_e( 'Pubblicato subito', 'scriba-ai' ); ?></option>
                                        </select>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_interval_hours"><?php esc_html_e( 'Intervallo (ore)', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <input type="number" min="1" max="168" id="scriba_ai_interval_hours" name="<?php echo esc_attr( SCRIBA_AI_OPTION_INTERVAL_HOURS ); ?>" value="<?php echo esc_attr( $interval ); ?>" class="small-text" />
                                        <p class="description"><?php esc_html_e( 'Ogni quante ore generare un nuovo articolo (1–168).', 'scriba-ai' ); ?></p>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_max_posts"><?php esc_html_e( 'Max articoli/giorno', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <input type="number" min="1" max="20" id="scriba_ai_max_posts" name="<?php echo esc_attr( SCRIBA_AI_OPTION_MAX_POSTS ); ?>" value="<?php echo esc_attr( $max_posts ); ?>" class="small-text" />
                                        <p class="description"><?php esc_html_e( 'Limite massimo di articoli auto-generati al giorno.', 'scriba-ai' ); ?></p>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_enable_logging"><?php esc_html_e( 'Logging', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <label>
                                            <input type="checkbox" id="scriba_ai_enable_logging" name="<?php echo esc_attr( SCRIBA_AI_OPTION_ENABLE_LOGGING ); ?>" value="1" <?php checked( $log_enabled, true ); ?> />
                                            <?php esc_html_e( 'Abilita logging dettagliato (consigliato in fase di test).', 'scriba-ai' ); ?>
                                        </label>
                                    </td>
                                </tr>
                                <tr>
                                    <th scope="row"><label for="scriba_ai_enable_images"><?php esc_html_e( 'Immagini automatiche', 'scriba-ai' ); ?></label></th>
                                    <td>
                                        <label>
                                            <input type="checkbox" id="scriba_ai_enable_images" name="<?php echo esc_attr( SCRIBA_AI_OPTION_ENABLE_IMAGES ); ?>" value="1" <?php checked( $enable_images, true ); ?> />
                                            <?php esc_html_e( 'Se attivo, SCRIBA userà prima le immagini dei feed; se non presenti proverà a generare una immagine di copertina con OpenAI.', 'scriba-ai' ); ?>
                                        </label>
                                    </td>
                                </tr>
                            </table>
                        </div>

                        <?php submit_button(); ?>
                    </form>

                    <div class="scriba-ai-section" style="background:#fff;padding:20px;margin-bottom:20px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);">
                        <h2><?php esc_html_e( 'Generazione manuale', 'scriba-ai' ); ?></h2>
                        <p><?php esc_html_e( 'Esegui SCRIBA ora per generare un articolo immediatamente (utile per test).', 'scriba-ai' ); ?></p>
                        <form method="post">
                            <?php wp_nonce_field( 'scriba_ai_generate_now_action' ); ?>
                            <input type="submit" name="scriba_ai_generate_now" class="button button-primary" value="<?php esc_attr_e( 'Genera articolo adesso', 'scriba-ai' ); ?>">
                        </form>
                    </div>
                </div>

                <div class="scriba-ai-sidebar" style="flex:1;min-width:300px;">
                    <div class="scriba-ai-section" style="background:#fff;padding:20px;margin-bottom:20px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);">
                        <h2><?php esc_html_e( 'Log attività (ultimi 50)', 'scriba-ai' ); ?></h2>
                        <div id="scriba-ai-logs" style="background:#f1f1f1;padding:10px;border:1px solid:#ddd;max-height:400px;overflow-y:auto;font-family:monospace;font-size:12px;">
                            <p class="description"><?php esc_html_e( 'Caricamento log...', 'scriba-ai' ); ?></p>
                        </div>
                        <p><button type="button" class="button" id="scriba-ai-refresh-logs"><?php esc_html_e( 'Aggiorna log', 'scriba-ai' ); ?></button></p>
                    </div>

                    <div class="scriba-ai-section" style="background:#fff;padding:20px;margin-bottom:20px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);">
                        <h2><?php esc_html_e( 'Informazioni', 'scriba-ai' ); ?></h2>
                        <ul>
                            <li><strong><?php esc_html_e( 'Versione:', 'scriba-ai' ); ?></strong> 1.4.0</li>
                            <li><strong><?php esc_html_e( 'Prossima esecuzione:', 'scriba-ai' ); ?></strong><br/>
                                <?php
                                $timestamp = wp_next_scheduled( 'scriba_ai_cron_event' );
                                if ( $timestamp ) {
                                    echo esc_html( date_i18n( 'd/m/Y H:i:s', $timestamp + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) );
                                } else {
                                    esc_html_e( 'Non programmato', 'scriba-ai' );
                                }
                                ?>
                            </li>
                            <li><strong><?php esc_html_e( 'Articoli generati:', 'scriba-ai' ); ?></strong><br/>
                                <?php
                                $generated = get_posts(
                                    array(
                                        'post_type'      => 'post',
                                        'meta_key'       => '_scriba_ai_generated',
                                        'meta_value'     => '1',
                                        'posts_per_page' => -1,
                                        'fields'         => 'ids',
                                    )
                                );
                                echo esc_html( (string) count( $generated ) );
                                ?>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>

        <script>
        (function($){
            $(document).ready(function(){
                var nonce = $('#scriba_ai_admin_nonce').val() || '';

                $('#scriba-ai-test-connection').on('click', function(){
                    var $btn = $(this);
                    var original = $btn.text();
                    $btn.prop('disabled', true).text('Test in corso...');

                    $.post(ajaxurl, {
                        action: 'scriba_ai_test_connection',
                        nonce: nonce
                    }, function(resp){
                        if (resp && resp.success) {
                            alert('OK: ' + resp.data);
                        } else if (resp && resp.data) {
                            alert('ERRORE: ' + resp.data);
                        } else {
                            alert('Risposta non valida dal server.');
                        }
                    }).always(function(){
                        $btn.prop('disabled', false).text(original);
                    });
                });

                function loadLogs(){
                    $('#scriba-ai-logs').html('<p class="description">Caricamento log...</p>');
                    $.post(ajaxurl, {
                        action: 'scriba_ai_get_logs',
                        nonce: nonce
                    }, function(resp){
                        if (resp && resp.success && resp.data && resp.data.length){
                            var html = '';
                            $.each(resp.data, function(i, log){
                                var typeClass = log.type ? log.type : 'info';
                                html += '<div class="scriba-ai-log-item ' + typeClass + '" style="padding:5px;margin-bottom:5px;border-left:3px solid #ddd;">';
                                html += '<div class="scriba-ai-log-time" style="color:#666;font-size:11px;">' + log.timestamp + '</div>';
                                html += '<div>' + log.message;
                                if (log.post_id){
                                    html += ' <a href="post.php?post=' + log.post_id + '&action=edit">(ID: ' + log.post_id + ')</a>';
                                }
                                html += '</div></div>';
                            });
                            $('#scriba-ai-logs').html(html);
                        } else {
                            $('#scriba-ai-logs').html('<p class="description">Nessun log disponibile</p>');
                        }
                    });
                }
                loadLogs();
                $('#scriba-ai-refresh-logs').on('click', loadLogs);
            });
        })(jQuery);
        </script>
        <?php
    }
}

endif;

SCRIBA_AI::get_instance();

Youez - 2016 - github.com/yon3zu
LinuXploit