| 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 : |
<?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();