Plugin Directory

Changeset 3398370


Ignore:
Timestamp:
11/18/2025 09:21:04 PM (5 months ago)
Author:
webtinus
Message:

Rest API optimization

Location:
aicontify
Files:
26 added
1 deleted
7 edited

Legend:

Unmodified
Added
Removed
  • aicontify/trunk/aicontify.php

    r3397131 r3398370  
    44Plugin URI: https://aicontify.com/
    55Description: Your Smart Content Assistant – One Click, Full Content
    6 Version: 4.0.2
     6Version: 5.0.0
    77Author: Hassan Solgi
    88Author URI: https://t.me/hassansolgi
     
    126126            true
    127127        );
    128 
    129         wp_localize_script(
    130             'aicont-post-tabs',
    131             'aicontApiSettings',
    132             [
    133                 'root'                      => esc_url_raw(rest_url()),
    134                 'nonce'                     => wp_create_nonce('wp_rest'),
    135                 'api_key'                   => esc_js(aicont_get_api_key()),
    136                 'site_language'             => esc_js(sanitize_text_field(get_option('aicont_plugin_site_language', 'en_US'))),
    137                 'site_title'                => esc_js(sanitize_text_field(get_option('aicont_plugin_site_title', ''))),
    138                 'api_endpoint'              => esc_url_raw(rest_url('aicont/v1/')),
    139                 'content_prompt_custom'     => esc_js(sanitize_textarea_field(get_option('aicont_plugin_content_prompt_custom', ''))),
    140                 'faq_prompt_custom'         => esc_js(sanitize_textarea_field(get_option('aicont_plugin_faq_prompt_custom', ''))),
    141                 'seo_title_prompt_custom'   => esc_js(sanitize_textarea_field(get_option('aicont_plugin_seo_title_prompt_custom', ''))),
    142                 'seo_meta_prompt_custom'    => esc_js(sanitize_textarea_field(get_option('aicont_plugin_seo_meta_prompt_custom', ''))),
    143                 'faq_article_id'            => esc_js(sanitize_text_field(get_option('aicont_plugin_faq_article_id', ''))),
    144                 'faq_question_id'           => esc_js(sanitize_text_field(get_option('aicont_plugin_faq_question_id', ''))),
    145                 'faq_answer_id'             => esc_js(sanitize_text_field(get_option('aicont_plugin_faq_answer_id', ''))),
    146                 'template_type'             => esc_js(sanitize_text_field(get_option('aicont_plugin_template_type', 'ready'))),
    147             ]
    148         );
     128       
     129        wp_localize_script('aicont-post-tabs', 'aicontify_ajax', [
     130            'ajax_url' => admin_url('admin-ajax.php'),
     131            'nonce'    => wp_create_nonce('aicont_nonce')
     132        ]);
    149133    }
    150134}
  • aicontify/trunk/contentPosts.php

    r3397005 r3398370  
    22if (!defined('ABSPATH')) exit;
    33
    4 // -------------------- REST API: Content Posts Generating (Client) --------------------
    5 add_action('rest_api_init', function() {
    6     register_rest_route('aicont/v1', '/generate_contentPostss', [
    7         'methods' => 'POST',
    8         'callback' => 'aicont_generate_content_posts_api',
    9         'permission_callback' => function() {
    10             return current_user_can('edit_posts');
    11         },
    12         'args' => [
    13             'keyword'       => ['required' => true,  'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    14             'post_id'       => ['required' => true,  'type' => 'integer', 'sanitize_callback' => 'absint'],
    15             'api_key'       => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    16             'site_language' => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    17             'site_title'    => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    18             'custom_prompt' => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_textarea_field'],
    19         ]
    20     ]);
    21 });
     4add_action('wp_ajax_aicont_generate_and_save_content', 'aicont_generate_and_save_content_handler');
    225
    23 function aicont_generate_content_posts_api($request) {
    24     global $aicont_site_language, $aicont_site_title;
     6function aicont_generate_and_save_content_handler() {
     7    check_ajax_referer('aicont_nonce', 'nonce');
    258
    26     $keyword       = $request->get_param('keyword');
    27     $post_id       = $request->get_param('post_id');
    28     $api_key       = $request->get_param('api_key');
    29     $site_language = $request->get_param('site_language') ?: $aicont_site_language;
    30     $site_title    = $request->get_param('site_title') ?: $aicont_site_title;
    31     $custom_prompt = $request->get_param('custom_prompt');
     9    $post_id = absint($_POST['post_id'] ?? 0);
     10    $keyword = sanitize_text_field($_POST['keyword'] ?? '');
     11    $custom_prompt = sanitize_textarea_field($_POST['custom_prompt'] ?? '');
    3212
    33     if (empty($keyword)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_keyword_empty'], 400);
    34     if (empty($post_id)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_id_invalid'], 400);
     13    if (!$post_id || !get_post($post_id)) {
     14        wp_send_json_error(['code' => 'err_post_id_invalid']);
     15    }
    3516
    36     $post = get_post($post_id);
    37     if (!$post) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_not_found'], 404);
     17    if (empty($keyword)) {
     18        wp_send_json_error(['code' => 'err_keyword_empty']);
     19    }
    3820
    39     // Priority: custom_prompt from AJAX > post meta > global option
     21    $post_title = get_the_title($post_id);
     22
    4023    if (empty($custom_prompt)) {
    4124        $custom_prompt = get_post_meta($post_id, 'aicont_singlepost_content_prompt_custom', true);
     
    4528    }
    4629
     30    $active_license = get_option('aicont_active_license', '');
     31    $active_license = $active_license ? trim(sanitize_text_field($active_license)) : '';
     32
     33    $result = aicont_call_webtinus_server([
     34        'keyword'       => $keyword,
     35        'post_id'       => $post_id,
     36        'post_title'    => $post_title,
     37        'prompt'        => $custom_prompt,
     38        'api_key'       => $active_license
     39    ]);
     40
     41    if (is_wp_error($result) || empty($result['content'])) {
     42        $error_code = $result['error_code'] ?? 'err_content_empty';
     43        wp_send_json_error(['code' => $error_code]);
     44    }
     45
     46    $updated = wp_update_post([
     47        'ID'           => $post_id,
     48        'post_content' => wp_kses_post($result['content'])
     49    ], true);
     50
     51    if (is_wp_error($updated)) {
     52        wp_send_json_error(['code' => 'err_save_failed']);
     53    }
     54
     55    wp_send_json_success([
     56        'message' => 'success',
     57        'content_length' => strlen($result['content'])
     58    ]);
     59}
     60
     61function aicont_call_webtinus_server($args) {
    4762    $client_site_url = home_url('', 'https');
    48     if (empty($client_site_url)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_site_url'], 500);
    49 
    50     $server_url = 'https://webtinus.com/wp-json/aicont/v1/generate_contentPostss';
     63    $server_url = 'https://webtinus.com/wp-json/aicont/v1/generate_contentPosts1';
    5164
    5265    $body = wp_json_encode([
    53         'keyword' => $keyword,
    54         'post_id' => $post_id,
    55         'prompt' => $custom_prompt,
    56         'api_key' => $api_key,
     66        'keyword'         => $args['keyword'],
     67        'post_id'         => $args['post_id'],
    5768        'client_site_url' => $client_site_url,
    58         'site_title' => $site_title,
    59         'site_language' => $site_language
     69        'post_title'      => $args['post_title'] ?? '',
     70        'prompt'          => $args['prompt'] ?? '',
     71        'api_key'         => $args['api_key'] ?? '',     
     72        'site_title'      => get_bloginfo('name'),
     73        'site_language'   => get_locale()
    6074    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    6175
    6276    $response = wp_remote_post($server_url, [
    63         'headers' => ['Content-Type' => 'application/json', 'X-Client-Site' => $client_site_url],
    64         'body' => $body,
    65         'timeout' => 120,
     77        'headers' => [
     78            'Content-Type'  => 'application/json',
     79            'X-Client-Site' => $client_site_url,
     80            'User-Agent'    => 'AiContify-WordPress-Plugin/1.0'
     81        ],
     82        'body'    => $body,
     83        'timeout' => 180,
    6684        'sslverify' => true
    6785    ]);
    6886
    6987    if (is_wp_error($response)) {
    70         return new WP_REST_Response(['success' => false, 'error_code' => 'err_connection_failed'], 500);
     88        return ['error_code' => 'err_connection_failed'];
    7189    }
    7290
    73     $status_code = wp_remote_retrieve_response_code($response);
    74     $body = wp_remote_retrieve_body($response);
    75     $data = json_decode($body, true);
     91    $response_code = wp_remote_retrieve_response_code($response);
     92    $response_body = wp_remote_retrieve_body($response);
     93    $data = json_decode($response_body, true);
    7694
    77     if ($status_code !== 200 || !isset($data['success'])) {
    78         $code = $data['error_code'] ?? 'err_server_error';
    79         return new WP_REST_Response(['success' => false, 'error_code' => $code], $status_code);
     95    if ($response_code !== 200 || empty($data['success'])) {
     96        $error_code = $data['error_code'] ?? 'err_server_error';
     97        return ['error_code' => $error_code];
    8098    }
    8199
    82     if (!$data['success'] || empty($data['content'])) {
    83         return new WP_REST_Response(['success' => false, 'error_code' => $data['error_code'] ?? 'err_content_empty'], 500);
     100    if (empty($data['content'])) {
     101        return ['error_code' => 'err_content_empty'];
    84102    }
    85103
    86     return new WP_REST_Response([
    87         'success' => true,
    88         'message_code' => 'success_content_generated',
    89         'content' => trim($data['content'])
    90     ], 200);
     104    return ['content' => trim($data['content'])];
    91105}
    92 ?>
  • aicontify/trunk/faqPosts.php

    r3397005 r3398370  
    22if (!defined('ABSPATH')) exit;
    33
    4 // -------------------- REST API: FAQ Generator (Client) --------------------
    5 add_action('rest_api_init', function() {
    6     register_rest_route('aicont/v1', '/generate_faqq', [
    7         'methods' => 'POST',
    8         'callback' => 'aicont_generate_faq_api',
    9         'permission_callback' => function() {
    10             return current_user_can('edit_posts');
    11         },
    12         'args' => [
    13             'keyword'       => ['required' => true,  'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    14             'post_id'       => ['required' => true,  'type' => 'integer', 'sanitize_callback' => 'absint'],
    15             'post_title'    => ['required' => true,  'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    16             'api_key'       => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    17             'site_language' => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    18             'site_title'    => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    19             'prompt'        => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_textarea_field'],
    20         ]
    21     ]);
    22 });
     4add_action('wp_ajax_aicont_generate_and_save_faq', 'aicont_generate_and_save_faq_handler');
    235
    24 function aicont_generate_faq_api($request) {
    25     global $aicont_api_key, $aicont_site_language, $aicont_site_title;
     6function aicont_generate_and_save_faq_handler() {
     7    check_ajax_referer('aicont_nonce', 'nonce');
    268
    27     $keyword       = $request->get_param('keyword');
    28     $post_id       = $request->get_param('post_id');
    29     $post_title    = $request->get_param('post_title');
    30     $api_key       = $request->get_param('api_key') ?: $aicont_api_key;
    31     $site_language = $request->get_param('site_language') ?: $aicont_site_language;
    32     $site_title    = $request->get_param('site_title') ?: $aicont_site_title;
    33     $prompt        = $request->get_param('prompt');
     9    $post_id = absint($_POST['post_id'] ?? 0);
     10    $keyword = sanitize_text_field($_POST['keyword'] ?? '');
     11    $custom_prompt = sanitize_textarea_field($_POST['custom_prompt'] ?? '');
    3412
    35     // Validation
    36     if (empty($keyword)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_keyword_empty'], 400);
    37     if (empty($post_id)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_id_invalid'], 400);
    38     if (empty($post_title)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_title_missing'], 400);
    39 
    40     $post = get_post($post_id);
    41     if (!$post) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_not_found'], 404);
    42 
    43     // Priority: custom_prompt from request > post meta > global option
    44     if (empty($prompt)) {
    45         $prompt = get_post_meta($post_id, 'aicont_singlepost_faq_prompt_custom', true);
     13    if (!$post_id || !get_post($post_id)) {
     14        wp_send_json_error(['code' => 'err_post_id_invalid']);
    4615    }
    47     if (empty($prompt)) {
    48         $prompt = get_option('aicont_plugin_faq_prompt_custom', '');
     16    if (empty($keyword)) {
     17        wp_send_json_error(['code' => 'err_keyword_empty']);
    4918    }
    5019
     20    $post_title = get_the_title($post_id);
     21    if (empty($post_title)) {
     22        wp_send_json_error(['code' => 'err_post_title_missing']);
     23    }
     24
     25    if (empty($custom_prompt)) {
     26        $custom_prompt = get_post_meta($post_id, 'aicont_singlepost_faq_prompt_custom', true);
     27    }
     28    if (empty($custom_prompt)) {
     29        $custom_prompt = get_option('aicont_plugin_faq_prompt_custom', '');
     30    }
     31
     32    $active_license = get_option('aicont_active_license', '');
     33
     34    $result = aicont_call_webtinus_faq([
     35        'keyword'     => $keyword,
     36        'post_id'     => $post_id,
     37        'post_title'  => $post_title,
     38        'prompt'      => $custom_prompt,
     39        'api_key'     => $active_license
     40    ]);
     41
     42    if (is_wp_error($result) || empty($result['faq_content'])) {
     43        wp_send_json_error(['code' => $result['error_code'] ?? 'err_faq_empty']);
     44    }
     45
     46    $saved = aicont_save_faq_to_post($post_id, $result['faq_content']);
     47    if (!$saved) {
     48        wp_send_json_error(['code' => 'err_save_failed']);
     49    }
     50
     51    wp_send_json_success(['message' => 'success_faq_generated']);
     52}
     53
     54function aicont_call_webtinus_faq($args) {
    5155    $client_site_url = home_url('', 'https');
    52     if (empty($client_site_url)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_site_url'], 500);
     56    $server_url = 'https://webtinus.com/wp-json/aicont/v1/generate_faq1';
    5357
    54     $server_url = 'https://webtinus.com/wp-json/aicont/v1/generate_faqq';
    5558    $body = wp_json_encode([
    56         'keyword'         => $keyword,
    57         'post_id'         => $post_id,
    58         'post_title'      => $post_title,
    59         'prompt'          => $prompt,
    60         'api_key'         => $api_key,
     59        'keyword'         => $args['keyword'],
     60        'post_id'         => $args['post_id'],
     61        'post_title'      => $args['post_title'],
     62        'prompt'          => $args['prompt'],
     63        'api_key'         => $args['api_key'] ?? '',
    6164        'client_site_url' => $client_site_url,
    62         'site_title'      => $site_title,
    63         'site_language'   => $site_language
     65        'site_title'      => get_bloginfo('name'),
     66        'site_language'   => get_locale()
    6467    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    6568
    6669    $response = wp_remote_post($server_url, [
    6770        'headers' => ['Content-Type' => 'application/json', 'X-Client-Site' => $client_site_url],
    68         'body' => $body,
    69         'timeout' => 120,
    70         'sslverify' => true
     71        'body'    => $body,
     72        'timeout' => 180
    7173    ]);
    7274
    73     if (is_wp_error($response)) {
    74         return new WP_REST_Response(['success' => false, 'error_code' => 'err_connection_failed'], 500);
     75    if (is_wp_error($response)) return ['error_code' => 'err_connection_failed'];
     76
     77    $data = json_decode(wp_remote_retrieve_body($response), true);
     78
     79    if (empty($data['success']) || empty($data['faq_content'])) {
     80        return ['error_code' => $data['error_code'] ?? 'err_faq_empty'];
    7581    }
    7682
    77     $status_code = wp_remote_retrieve_response_code($response);
    78     $body = wp_remote_retrieve_body($response);
    79     $data = json_decode($body, true);
     83    return ['faq_content' => trim($data['faq_content'])];
     84}
    8085
    81     if ($status_code !== 200 || !isset($data['success'])) {
    82         $code = $data['error_code'] ?? 'err_server_error';
    83         return new WP_REST_Response(['success' => false, 'error_code' => $code], $status_code);
    84     }
    85 
    86     if (!$data['success'] || empty($data['faq_content'])) {
    87         return new WP_REST_Response(['success' => false, 'error_code' => $data['error_code'] ?? 'err_faq_empty'], 500);
    88     }
    89 
    90     $faq_content = trim($data['faq_content']);
     86function aicont_save_faq_to_post($post_id, $faq_content) {
    9187    $lines = array_filter(preg_split('/\r\n|\r|\n/', $faq_content), 'trim');
    92 
    9388    $template_type = get_option('aicont_plugin_template_type', 'ready');
    94     $faq_html = '';
    9589
    9690    if ($template_type === 'custom') {
    97         $article_class  = sanitize_html_class(get_option('aicont_plugin_faq_article_id', 'faq-article'));
    98         $question_class = sanitize_html_class(get_option('aicont_plugin_faq_question_id', 'faq-question'));
    99         $answer_class   = sanitize_html_class(get_option('aicont_plugin_faq_answer_id', 'faq-answer'));
     91        if (!function_exists('update_field')) {
     92            return false;
     93        }
     94        $repeater = get_option('aicont_plugin_faq_article_id', 'faq_post_single');
     95        $q_field  = get_option('aicont_plugin_faq_question_id', 'tit_faq_post_single');
     96        $a_field  = get_option('aicont_plugin_faq_answer_id', 'desc_faq_post_single');
    10097
    101         $faq_html .= "<article class='" . esc_attr($article_class) . "'>";
    102         $faq_html .= "<h2>" . esc_html__('Frequently Asked Questions', 'aicontify') . "</h2>";
    103         for ($i = 0; $i < count($lines); $i += 2) {
    104             $question = trim($lines[$i] ?? '');
    105             $answer   = trim($lines[$i + 1] ?? '');
    106             if ($question && $answer) {
    107                 $faq_html .= "<h3 class='" . esc_attr($question_class) . "'>" . esc_html($question) . "</h3>";
    108                 $faq_html .= "<p class='" . esc_attr($answer_class) . "'>" . wp_kses_post($answer) . "</p>";
     98        delete_field($repeater, $post_id);
     99        foreach (array_chunk($lines, 2) as $pair) {
     100            if (count($pair) === 2) {
     101                add_row($repeater, [$q_field => $pair[0], $a_field => $pair[1]], $post_id);
    109102            }
    110103        }
    111         $faq_html .= "</article>";
    112104    } else {
    113         $faq_html .= "<div class='aicont-faq-section'>";
    114         $faq_html .= "<h2>" . esc_html__('Frequently Asked Questions', 'aicontify') . "</h2>";
    115         for ($i = 0; $i < count($lines); $i += 2) {
    116             $question = trim($lines[$i] ?? '');
    117             $answer   = trim($lines[$i + 1] ?? '');
    118             if ($question && $answer) {
    119                 $faq_html .= "<h3>" . esc_html($question) . "</h3>";
    120                 $faq_html .= "<p>" . wp_kses_post($answer) . "</p>";
     105        $faq_html = "<div class='aicont-faq-section'><h2>" . __('Frequently Asked Questions', 'aicontify') . "</h2>";
     106        foreach (array_chunk($lines, 2) as $pair) {
     107            if (count($pair) === 2) {
     108                $faq_html .= "<h3>" . esc_html($pair[0]) . "</h3>";
     109                $faq_html .= "<p>" . wp_kses_post($pair[1]) . "</p>";
    121110            }
    122111        }
    123112        $faq_html .= "</div>";
     113
     114        $post = get_post($post_id);
     115        $new_content = $post->post_content . "\n" . $faq_html;
     116
     117        wp_update_post([
     118            'ID' => $post_id,
     119            'post_content' => $new_content
     120        ], true);
    124121    }
    125122
    126     $saved = 0;
    127     if ($template_type === 'custom') {
    128         $acf_repeater_field = get_option('aicont_plugin_faq_article_id', 'faq_post_single');
    129         $acf_question_field = get_option('aicont_plugin_faq_question_id', 'tit_faq_post_single');
    130         $acf_answer_field   = get_option('aicont_plugin_faq_answer_id', 'desc_faq_post_single');
    131 
    132         if (!function_exists('delete_field') || !function_exists('add_row')) {
    133             return new WP_REST_Response(['success' => false, 'error_code' => 'err_acf_missing'], 400);
    134         }
    135 
    136         delete_field($acf_repeater_field, $post_id);
    137         for ($i = 0; $i < count($lines); $i += 2) {
    138             $question = trim($lines[$i] ?? '');
    139             $answer   = trim($lines[$i + 1] ?? '');
    140             if ($question && $answer) {
    141                 $added = add_row($acf_repeater_field, [
    142                     $acf_question_field => $question,
    143                     $acf_answer_field   => $answer,
    144                 ], $post_id);
    145                 if ($added) $saved++;
    146             }
    147         }
    148 
    149         if ($saved === 0) {
    150             return new WP_REST_Response(['success' => false, 'error_code' => 'err_save_failed'], 500);
    151         }
    152 
    153     } else {
    154         $current_content = $post->post_content;
    155         $post_data = [
    156             'ID' => $post_id,
    157             'post_content' => $current_content . "\n" . wp_kses_post($faq_html),
    158             'post_modified' => current_time('mysql'),
    159             'post_modified_gmt' => current_time('mysql', 1),
    160         ];
    161         $post_updated = wp_update_post($post_data, true);
    162         if (is_wp_error($post_updated)) {
    163             return new WP_REST_Response(['success' => false, 'error_code' => 'err_save_failed'], 500);
    164         }
    165     }
    166 
    167     return new WP_REST_Response([
    168         'success' => true,
    169         'message_code' => 'success_faq_generated',
    170         'faq_content' => $faq_content,
    171         'faq_html' => $faq_html
    172     ], 200);
     123    return true;
    173124}
  • aicontify/trunk/js/tabsPosts.js

    r3397005 r3398370  
    5656  });
    5757
    58   // Var global
     58  // global
    5959  const keywordInput = document.getElementById("aicont_keyword");
    6060
    61   // AiContify Content Generator
    62   const contentBtn = document.getElementById(
     61  /**
     62   * AiContify - Content Generator
     63   */
     64
     65  const generateBtn = document.getElementById(
    6366    "aicont-generate-contentPosts-btn"
    6467  );
    65   const contentResultBox = document.getElementById(
    66     "aicont-contentPosts-result"
    67   );
    68   const contentLoadingBox = document.getElementById("aicont-loading-box");
    69 
    70   // --- Message Translator ---
    71   function getContentMessage(code) {
    72     const messages = {
    73       err_keyword_empty:
    74         "Please enter a keyword in the field above the tabs before generating content.",
    75       err_post_id_invalid: "Invalid post ID.",
    76       err_wp_settings_missing:
    77         "WordPress API settings are missing. Please check plugin configuration.",
     68  const resultBox = document.getElementById("aicont-contentPosts-result");
     69  const loadingBox = document.getElementById("aicont-loading-box");
     70
     71  if (!keywordInput || !generateBtn || !resultBox || !loadingBox) {
     72    console.warn("AiContify: Required elements not found.");
     73    return;
     74  }
     75
     76  function toggleGenerateButton() {
     77    const hasKeyword = keywordInput.value.trim().length > 0;
     78    generateBtn.disabled = !hasKeyword;
     79    generateBtn.style.opacity = hasKeyword ? "1" : "0.5";
     80    generateBtn.style.cursor = hasKeyword ? "pointer" : "not-allowed";
     81  }
     82
     83  keywordInput.addEventListener("input", toggleGenerateButton);
     84  keywordInput.addEventListener("paste", () =>
     85    setTimeout(toggleGenerateButton, 100)
     86  );
     87  toggleGenerateButton();
     88
     89  function getMessage(key) {
     90    const translations = {
     91      generating: "Generating and saving content, please wait...",
     92      success_generated: "Content generated successfully!",
     93      saving_post: "Saving post...",
     94      page_refreshing: "Page is refreshing to display changes...",
     95      err_keyword_empty: "Please enter a keyword before generating content.",
    7896      err_connection_failed:
    79         "Failed to connect to the server. Please check your internet.",
    80       err_server_error: "Server error occurred.",
    81       err_invalid_response: "Invalid response from server.",
     97        "Connection failed. Please check your internet connection.",
     98      err_server_error: "An unexpected server error occurred.",
    8299      err_content_empty: "Generated content is empty.",
    83       err_save_timeout: "Save operation timed out.",
    84100      err_save_failed: "Failed to save the post.",
    85       err_invalid_site_url: "Client site URL is invalid.",
    86101      err_daily_limit_exceeded:
    87         "Daily AI post limit reached. Upgrade to Pro or try tomorrow.",
    88 
    89       success_content_generated: "Content generated and inserted. Saving...",
    90       success_content_saved: "Content saved successfully.",
     102        "Daily AI generation limit reached. Try again tomorrow or upgrade.",
    91103    };
    92     return wp.i18n.__(messages[code] || "Unknown error occurred.", "aicontify");
    93   }
    94 
    95   function getPostTitle() {
    96     if (wp?.data?.select("core/editor")) {
    97       const gutenbergTitle = wp.data
    98         .select("core/editor")
    99         .getEditedPostAttribute("title")
    100         ?.trim();
    101       if (gutenbergTitle) {
    102         return gutenbergTitle;
    103       }
    104     }
    105     const classicTitleInput = document.getElementById("title");
    106     if (classicTitleInput && classicTitleInput.value?.trim()) {
    107       return classicTitleInput.value.trim();
    108     }
    109     const metaInput = document.querySelector('input[name="post_title"]');
    110     if (metaInput && metaInput.value?.trim()) {
    111       return metaInput.value.trim();
    112     }
    113     return null;
    114   }
    115 
    116   function insertContent(content) {
    117     if (wp?.data?.dispatch("core/editor")) {
    118       wp.data.dispatch("core/editor").editPost({ content: content });
    119       return true;
    120     }
    121     if (typeof tinyMCE !== "undefined" && tinyMCE.activeEditor) {
    122       tinyMCE.activeEditor.setContent(content);
    123       return true;
    124     }
    125     const contentTextarea = document.getElementById("content");
    126     if (contentTextarea) {
    127       contentTextarea.value = content;
    128       contentTextarea.dispatchEvent(new Event("change", { bubbles: true }));
    129       return true;
    130     }
    131     return false;
    132   }
    133 
    134   async function savePost() {
    135     if (wp?.data?.dispatch("core/editor")) {
    136       try {
    137         await wp.data.dispatch("core/editor").savePost();
    138         let isSaved = false;
    139         for (let i = 0; i < 15; i++) {
    140           await new Promise((r) => setTimeout(r, 500));
    141           if (!wp.data.select("core/editor").isSavingPost()) {
    142             isSaved = true;
    143             break;
    144           }
    145         }
    146         if (!isSaved) throw new Error("err_save_timeout");
    147         return true;
    148       } catch (err) {
    149         throw new Error(
    150           err.message.includes("timeout")
    151             ? "err_save_timeout"
    152             : "err_save_failed"
    153         );
    154       }
    155     }
    156 
    157     const postForm = document.getElementById("post");
    158     if (postForm) {
    159       try {
    160         const publishButton =
    161           document.getElementById("publish") ||
    162           document.querySelector('button[type="submit"]');
    163         if (publishButton) {
    164           publishButton.click();
    165           await new Promise((r) => setTimeout(r, 2000));
    166           return true;
    167         }
    168       } catch (err) {
    169         throw new Error("err_save_failed");
    170       }
    171     }
    172     throw new Error("err_save_failed");
    173   }
    174 
    175   function toggleGenerateButton() {
    176     const keyword = keywordInput?.value?.trim();
    177     if (keyword && keyword.length > 0) {
    178       contentBtn.disabled = false;
    179       contentBtn.style.opacity = "1";
    180       contentBtn.style.cursor = "pointer";
    181     } else {
    182       contentBtn.disabled = true;
    183       contentBtn.style.opacity = "0.5";
    184       contentBtn.style.cursor = "not-allowed";
    185     }
    186   }
    187 
    188   if (contentBtn && keywordInput) {
    189     toggleGenerateButton();
    190     keywordInput.addEventListener("input", toggleGenerateButton);
    191     keywordInput.addEventListener("paste", () =>
    192       setTimeout(toggleGenerateButton, 0)
    193     );
    194   }
    195 
    196   // Content Generation Handler
    197   if (contentBtn) {
    198     contentBtn.addEventListener("click", async function () {
    199       const postId = this.getAttribute("data-postid");
    200       const keyword = keywordInput?.value?.trim();
    201 
    202       if (!postId || isNaN(postId)) {
    203         contentResultBox.innerHTML = `<p class="aicont-error">${getContentMessage(
    204           "err_post_id_invalid"
    205         )}</p>`;
    206         return;
    207       }
    208 
    209       if (!keyword || keyword.length === 0) {
    210         contentResultBox.innerHTML = `<p class="aicont-error">${getContentMessage(
    211           "err_keyword_empty"
    212         )}</p>`;
    213         return;
    214       }
    215 
    216       contentLoadingBox.style.display = "block";
    217       contentResultBox.innerHTML = "";
    218 
    219       try {
    220         const singlePostPrompt =
    221           document
    222             .getElementById("aicont_singlepost_content_prompt_custom")
    223             ?.value?.trim() || "";
    224         const globalPrompt = aicont?.contentPrompt || "";
    225 
    226         const customPrompt = singlePostPrompt || globalPrompt || "";
    227 
    228         const response = await fetch(aicont.ajaxUrl, {
    229           method: "POST",
    230           headers: {
    231             "Content-Type": "application/x-www-form-urlencoded",
    232           },
    233           body: new URLSearchParams({
    234             action: "aicont_generate_content",
    235             nonce: aicont.nonce,
    236             post_id: postId,
    237             keyword: keyword,
    238             custom_prompt: customPrompt,
    239           }).toString(),
    240         });
    241 
    242         const data = await response.json();
    243 
    244         if (!data.success) {
    245           throw new Error(data.data?.message || "err_server_error");
    246         }
    247 
    248         if (!data.data?.content || data.data.content.trim().length === 0) {
    249           throw new Error("err_content_empty");
    250         }
    251 
    252         const isInserted = insertContent(data.data.content);
    253         if (!isInserted) {
    254           throw new Error("err_invalid_response");
    255         }
    256 
    257         contentResultBox.innerHTML = `<p class="aicont-success">${getContentMessage(
    258           "success_content_generated"
    259         )}</p>`;
    260 
    261         await savePost();
    262 
    263         contentResultBox.innerHTML = `<p class="aicont-success">${getContentMessage(
    264           "success_content_saved"
    265         )}</p>`;
    266         contentLoadingBox.style.display = "none";
    267       } catch (error) {
    268         contentLoadingBox.style.display = "none";
    269         const errorCode =
    270           error.message === "err_save_timeout" ||
    271           error.message === "err_save_failed" ||
    272           error.message === "err_invalid_response" ||
    273           error.message === "err_content_empty" ||
    274           error.message === "err_keyword_empty" ||
    275           error.message === "err_post_id_invalid"
    276             ? error.message
    277             : "err_server_error";
    278 
    279         contentResultBox.innerHTML = `<p class="aicont-error">${getContentMessage(
    280           errorCode
    281         )}</p>`;
    282 
    283         console.error("AiContify Content Generation Error:", error);
    284       }
    285     });
    286   }
    287 
    288   // --- Content Generator ---
    289   if (contentBtn) {
    290     contentBtn.addEventListener("click", async function () {
    291       const keyword = keywordInput?.value?.trim();
    292       if (!keyword) {
    293         contentResultBox.innerHTML = `<span class='error'>${getContentMessage(
    294           "err_keyword_empty"
    295         )}</span>`;
    296         return;
    297       }
    298 
    299       const postId = contentBtn.dataset.postid;
    300       if (!postId || isNaN(postId)) {
    301         contentResultBox.innerHTML = `<span class='error'>${getContentMessage(
    302           "err_post_id_invalid"
    303         )}</span>`;
    304         return;
    305       }
    306 
    307       if (
    308         !window.aicontApiSettings ||
    309         !aicontApiSettings.root ||
    310         !aicontApiSettings.nonce
    311       ) {
    312         contentResultBox.innerHTML = `<span class='error'>${getContentMessage(
    313           "err_wp_settings_missing"
    314         )}</span>`;
    315         return;
    316       }
    317 
    318       contentBtn.disabled = true;
    319       contentBtn.textContent = wp.i18n.__("Generating...", "aicontify");
    320       contentResultBox.innerHTML = "";
    321       contentLoadingBox.style.display = "block";
    322 
    323       try {
    324         const requestData = {
    325           keyword,
    326           post_id: parseInt(postId),
    327           api_key: aicontApiSettings.api_key || "",
    328           site_language: aicontApiSettings.site_language,
    329           site_title: aicontApiSettings.site_title,
    330           prompt: aicontApiSettings.content_prompt_custom || "",
    331         };
    332 
    333         const response = await fetch(
    334           aicontApiSettings.root + "aicont/v1/generate_contentPostss",
    335           {
    336             method: "POST",
    337             headers: {
    338               "Content-Type": "application/json",
    339               "X-WP-Nonce": aicontApiSettings.nonce,
    340             },
    341             body: JSON.stringify(requestData),
    342           }
    343         );
    344 
    345         let data;
    346         try {
    347           data = await response.json();
    348         } catch (jsonErr) {
    349           throw new Error("err_invalid_response");
    350         }
    351 
    352         if (!response.ok) {
    353           const code = data.error_code || "err_server_error";
    354           throw new Error(code);
    355         }
    356 
    357         if (data.success && data.content) {
    358           if (!insertContent(data.content)) {
    359             throw new Error("err_save_failed");
    360           }
    361 
    362           contentResultBox.innerHTML = `<span class='success'>${getContentMessage(
    363             "success_content_generated"
    364           )}</span><br><span style='color: #0073aa;'>${wp.i18n.__(
    365             "Saving post...",
    366             "aicontify"
    367           )}</span>`;
    368 
    369           await savePost();
    370 
    371           contentResultBox.innerHTML = `<span class='success'>${getContentMessage(
    372             "success_content_saved"
    373           )}</span><br><span style='color: #0073aa;'>${wp.i18n.__(
    374             "Page is refreshing to display changes...",
    375             "aicontify"
    376           )}</span>`;
    377 
    378           setTimeout(() => location.reload(), 1500);
    379         } else {
    380           throw new Error(data.error_code || "err_invalid_response");
    381         }
    382       } catch (err) {
    383         const code =
    384           typeof err.message === "string" && err.message.startsWith("err_")
    385             ? err.message
    386             : "err_server_error";
    387         contentResultBox.innerHTML = `<span class='error'>${getContentMessage(
    388           code
    389         )}</span>`;
    390       } finally {
    391         toggleGenerateButton();
    392         contentBtn.textContent = wp.i18n.__(
    393           "Generate Main Content",
    394           "aicontify"
    395         );
    396         contentLoadingBox.style.display = "none";
    397       }
    398     });
    399   }
    400 
    401   // --- FAQ Generator ---
     104    return wp.i18n.__(translations[key] || key, "aicontify");
     105  }
     106
     107  generateBtn.addEventListener("click", async function (e) {
     108    e.preventDefault();
     109
     110    const postId = generateBtn.getAttribute("data-postid");
     111    const keyword = keywordInput.value.trim();
     112
     113    resultBox.innerHTML = "";
     114    loadingBox.style.display = "block";
     115    resultBox.innerHTML = `<span style='color: #0073aa;'>${getMessage(
     116      "generating"
     117    )}</span>`;
     118
     119    if (!keyword) {
     120      resultBox.innerHTML = `<span class='error'>${getMessage(
     121        "err_keyword_empty"
     122      )}</span>`;
     123      loadingBox.style.display = "none";
     124      return;
     125    }
     126
     127    try {
     128      const customPromptEl = document.getElementById(
     129        "aicont_singlepost_content_prompt_custom"
     130      );
     131      const customPrompt = customPromptEl ? customPromptEl.value.trim() : "";
     132
     133      const formData = new URLSearchParams();
     134      formData.append("action", "aicont_generate_and_save_content");
     135      formData.append("nonce", aicontify_ajax.nonce);
     136      formData.append("post_id", postId);
     137      formData.append("keyword", keyword);
     138      formData.append("custom_prompt", customPrompt);
     139
     140      const response = await fetch(aicontify_ajax.ajax_url, {
     141        method: "POST",
     142        credentials: "same-origin",
     143        headers: {
     144          "Content-Type": "application/x-www-form-urlencoded",
     145        },
     146        body: formData,
     147      });
     148
     149      const data = await response.json();
     150
     151      if (!response.ok || !data.success) {
     152        const code = data.data?.code || "err_server_error";
     153        throw new Error(code);
     154      }
     155
     156      resultBox.innerHTML = `
     157        <span class='success'>${getMessage("success_generated")}</span><br>
     158        <span style='color: #0073aa;'>${getMessage("saving_post")}</span>
     159      `;
     160
     161      setTimeout(() => {
     162        resultBox.innerHTML = `
     163          <span class='success'>${getMessage("success_generated")}</span><br>
     164          <span style='color: #0073aa;'>${getMessage("page_refreshing")}</span>
     165        `;
     166        setTimeout(() => location.reload(), 1200);
     167      }, 800);
     168    } catch (error) {
     169      console.error("AiContify Error:", error);
     170
     171      const errorCode = error.message;
     172      const errorMessages = {
     173        err_keyword_empty: "err_keyword_empty",
     174        err_connection_failed: "err_connection_failed",
     175        err_server_error: "err_server_error",
     176        err_content_empty: "err_content_empty",
     177        err_save_failed: "err_save_failed",
     178        err_daily_limit_exceeded: "err_daily_limit_exceeded",
     179      };
     180
     181      const msgKey = errorMessages[errorCode] || "err_server_error";
     182      resultBox.innerHTML = `<span class='error'>${getMessage(msgKey)}</span>`;
     183    } finally {
     184      loadingBox.style.display = "none";
     185    }
     186  });
     187
     188  // --- FAQ Generator (PHP-Only + AJAX) ---
    402189  const faqBtn = document.getElementById("aicont-generate-faq-btn");
    403190  const faqResultBox = document.getElementById("aicont-faq-result");
    404191  const faqLoadingBox = document.getElementById("aicont-faq-loading-box");
    405192
    406   // --- Message Translator for FAQ ---
    407   function getFaqMessage(code) {
    408     const messages = {
    409       // Errors
    410       err_keyword_empty:
    411         "Please enter a keyword in the field above the tabs before generating FAQs.",
    412       err_post_id_invalid: "Invalid post ID.",
    413       err_wp_settings_missing:
    414         "WordPress API settings are missing. Please check plugin configuration.",
    415       err_connection_failed:
    416         "Failed to connect to the server. Please check your internet.",
    417       err_server_error: "Server error occurred.",
    418       err_invalid_response: "Invalid response from server.",
    419       err_faq_empty: "Generated FAQ content is empty.",
    420       err_acf_missing:
    421         "ACF functions are not available. Ensure ACF Pro is installed and active.",
    422       err_save_failed: "Failed to save FAQ items.",
    423       err_post_title_missing: "Post title is missing.",
    424       err_daily_limit_exceeded:
    425         "Daily AI post limit reached. Upgrade to Pro or try tomorrow.",
    426 
    427       // Success
    428       success_faq_generated: "FAQs generated and saved successfully!",
    429     };
    430 
    431     return wp.i18n.__(messages[code] || "Unknown error occurred.", "aicontify");
    432   }
    433 
    434   function getPostTitle() {
    435     if (wp?.data?.select("core/editor")) {
    436       const gutenbergTitle = wp.data
    437         .select("core/editor")
    438         .getEditedPostAttribute("title")
    439         ?.trim();
    440       if (gutenbergTitle) {
    441         return gutenbergTitle;
    442       }
    443     }
    444 
    445     const classicTitleInput = document.getElementById("title");
    446     if (classicTitleInput && classicTitleInput.value?.trim()) {
    447       return classicTitleInput.value.trim();
    448     }
    449 
    450     const metaInput = document.querySelector('input[name="post_title"]');
    451     if (metaInput && metaInput.value?.trim()) {
    452       return metaInput.value.trim();
    453     }
    454 
    455     return null;
    456   }
    457 
    458   function toggleFaqButton() {
    459     const keyword = keywordInput?.value?.trim();
    460     if (keyword && keyword.length > 0) {
    461       faqBtn.disabled = false;
    462       faqBtn.style.opacity = "1";
    463       faqBtn.style.cursor = "pointer";
    464     } else {
    465       faqBtn.disabled = true;
    466       faqBtn.style.opacity = "0.5";
    467       faqBtn.style.cursor = "not-allowed";
    468     }
    469   }
    470 
    471   if (faqBtn && keywordInput) {
    472     toggleFaqButton();
     193  if (faqBtn && keywordInput && faqResultBox && faqLoadingBox) {
     194    function toggleFaqButton() {
     195      const hasKeyword = keywordInput.value.trim().length > 0;
     196      faqBtn.disabled = !hasKeyword;
     197      faqBtn.style.opacity = hasKeyword ? "1" : "0.5";
     198      faqBtn.style.cursor = hasKeyword ? "pointer" : "not-allowed";
     199    }
     200
    473201    keywordInput.addEventListener("input", toggleFaqButton);
    474202    keywordInput.addEventListener("paste", () =>
    475       setTimeout(toggleFaqButton, 0)
     203      setTimeout(toggleFaqButton, 100)
    476204    );
    477   }
    478 
    479   if (faqBtn) {
    480     faqBtn.addEventListener("click", async function () {
    481       const keyword = keywordInput?.value?.trim();
     205    toggleFaqButton();
     206
     207    function getFaqMessage(key) {
     208      const translations = {
     209        generating: "Generating FAQs, please wait...",
     210        success_generated: "FAQs generated and saved successfully!",
     211        saving_post: "Saving FAQs to post...",
     212        page_refreshing: "Page is refreshing to display changes...",
     213        err_keyword_empty: "Please enter a keyword before generating FAQs.",
     214        err_server_error: "Server error occurred while generating FAQs.",
     215        err_save_failed: "Failed to save FAQ items.",
     216        err_acf_missing: "ACF Pro is required for custom FAQ templates.",
     217        err_daily_limit_exceeded:
     218          "Daily AI generation limit reached. Upgrade or try tomorrow.",
     219      };
     220      return wp.i18n.__(translations[key] || key, "aicontify");
     221    }
     222
     223    faqBtn.addEventListener("click", async function (e) {
     224      e.preventDefault();
     225
     226      const postId = faqBtn.getAttribute("data-postid");
     227      const keyword = keywordInput.value.trim();
     228
     229      faqResultBox.innerHTML = "";
     230      faqLoadingBox.style.display = "block";
     231      faqResultBox.innerHTML = `<span style='color: #0073aa;'>${getFaqMessage(
     232        "generating"
     233      )}</span>`;
     234
    482235      if (!keyword) {
    483236        faqResultBox.innerHTML = `<span class='error'>${getFaqMessage(
    484237          "err_keyword_empty"
    485238        )}</span>`;
     239        faqLoadingBox.style.display = "none";
    486240        return;
    487241      }
    488242
    489       const postId = faqBtn.dataset.postid;
    490       if (!postId || isNaN(postId)) {
    491         faqResultBox.innerHTML = `<span class='error'>${getFaqMessage(
    492           "err_post_id_invalid"
    493         )}</span>`;
    494         return;
    495       }
    496 
    497       if (
    498         !window.aicontApiSettings ||
    499         !aicontApiSettings.root ||
    500         !aicontApiSettings.nonce
    501       ) {
    502         faqResultBox.innerHTML = `<span class='error'>${getFaqMessage(
    503           "err_wp_settings_missing"
    504         )}</span>`;
    505         return;
    506       }
    507 
    508       faqBtn.disabled = true;
    509       faqBtn.textContent = wp.i18n.__("Generating FAQs...", "aicontify");
    510       faqResultBox.innerHTML = "";
    511       faqLoadingBox.style.display = "block";
    512 
    513243      try {
    514         const postTitle = getPostTitle();
    515         if (!postTitle) throw new Error("err_post_title_missing");
    516 
    517         const singlePostPrompt =
    518           document
    519             .getElementById("aicont_singlepost_faq_prompt_custom")
    520             ?.value?.trim() || "";
    521         const globalPrompt = aicontApiSettings.faq_prompt_custom || "";
    522 
    523         const customPrompt = singlePostPrompt || globalPrompt || "";
    524 
    525         const requestData = {
    526           keyword,
    527           post_id: parseInt(postId),
    528           post_title: postTitle,
    529           api_key: aicontApiSettings.api_key || "",
    530           site_language: aicontApiSettings.site_language,
    531           site_title: aicontApiSettings.site_title,
    532           prompt: customPrompt,
    533         };
    534 
    535         const response = await fetch(
    536           aicontApiSettings.root + "aicont/v1/generate_faqq",
    537           {
    538             method: "POST",
    539             headers: {
    540               "Content-Type": "application/json",
    541               "X-WP-Nonce": aicontApiSettings.nonce,
    542             },
    543             body: JSON.stringify(requestData),
    544           }
     244        const customPromptEl = document.getElementById(
     245          "aicont_singlepost_faq_prompt_custom"
    545246        );
    546 
    547         let data;
    548         try {
    549           data = await response.json();
    550         } catch (jsonErr) {
    551           throw new Error("err_invalid_response");
    552         }
    553 
    554         if (!response.ok) {
    555           const code = data.error_code || "err_server_error";
     247        const customPrompt = customPromptEl ? customPromptEl.value.trim() : "";
     248
     249        const formData = new URLSearchParams();
     250        formData.append("action", "aicont_generate_and_save_faq");
     251        formData.append("nonce", aicontify_ajax.nonce);
     252        formData.append("post_id", postId);
     253        formData.append("keyword", keyword);
     254        formData.append("custom_prompt", customPrompt);
     255
     256        const response = await fetch(aicontify_ajax.ajax_url, {
     257          method: "POST",
     258          credentials: "same-origin",
     259          headers: { "Content-Type": "application/x-www-form-urlencoded" },
     260          body: formData,
     261        });
     262
     263        const data = await response.json();
     264
     265        if (!response.ok || !data.success) {
     266          const code = data.data?.code || "err_server_error";
    556267          throw new Error(code);
    557268        }
    558269
    559         if (data.success && data.faq_content) {
    560           faqResultBox.innerHTML = `
    561         <span class='success'>${getFaqMessage(
    562           "success_faq_generated"
    563         )}</span><br>
    564         <span style='color: #0073aa;'>${wp.i18n.__(
    565           "Page is refreshing to display changes...",
    566           "aicontify"
    567         )}</span>
     270        faqResultBox.innerHTML = `
     271        <span class='success'>${getFaqMessage("success_generated")}</span><br>
     272        <span style='color: #0073aa;'>${getFaqMessage("page_refreshing")}</span>
    568273      `;
    569           setTimeout(() => location.reload(), 1500);
    570         } else {
    571           throw new Error(data.error_code || "err_invalid_response");
    572         }
    573       } catch (err) {
    574         const code =
    575           typeof err.message === "string" && err.message.startsWith("err_")
    576             ? err.message
    577             : "err_server_error";
     274
     275        setTimeout(() => location.reload(), 1500);
     276      } catch (error) {
     277        console.error("AiContify FAQ Error:", error);
     278        const code = error.message;
     279        const msg =
     280          {
     281            err_keyword_empty: "err_keyword_empty",
     282            err_save_failed: "err_save_failed",
     283            err_acf_missing: "err_acf_missing",
     284            err_daily_limit_exceeded: "err_daily_limit_exceeded",
     285          }[code] || "err_server_error";
    578286
    579287        faqResultBox.innerHTML = `<span class='error'>${getFaqMessage(
    580           code
     288          msg
    581289        )}</span>`;
    582290      } finally {
    583         toggleFaqButton();
    584         faqBtn.textContent = wp.i18n.__("Generate FAQs", "aicontify");
    585291        faqLoadingBox.style.display = "none";
    586292      }
     
    588294  }
    589295
    590   // --- SEO Title Generator ---
     296  // --- SEO Title Generator (PHP-Only + AJAX) ---
    591297  const seoTitleBtn = document.getElementById("aicont-generate-seo-title-btn");
    592298  const seoTitleResultBox = document.getElementById("aicont-seo-title-result");
     
    595301  );
    596302
    597   // --- Message Translator for SEO Title ---
    598   function getSeoTitleMessage(code) {
    599     const messages = {
    600       // Errors
    601       err_keyword_empty:
    602         "Please enter a keyword in the field above the tabs before generating SEO title.",
    603       err_post_id_invalid: "Invalid post ID.",
    604       err_wp_settings_missing:
    605         "WordPress API settings (API key or endpoint) are missing or incomplete. Please check plugin configuration.",
    606       err_license_required:
    607         "API key is required for non-free models. Please configure a valid API key in the plugin settings.",
    608       err_connection_failed:
    609         "Failed to connect to the server. Please check your internet.",
    610       err_server_error: "Server error occurred.",
    611       err_invalid_response: "Invalid response from server.",
    612       err_title_empty: "Generated SEO title is empty.",
    613       err_save_failed: "Failed to save SEO title.",
    614       err_post_title_missing: "Post title is missing.",
    615       err_daily_limit_exceeded:
    616         "Daily AI post limit reached. Upgrade to Pro or try tomorrow.",
    617 
    618       // Success
    619       success_seo_title_generated:
    620         "SEO title generated and saved successfully!",
    621     };
    622 
    623     return wp.i18n.__(messages[code] || "Unknown error occurred.", "aicontify");
    624   }
    625 
    626   function getPostTitle() {
    627     // 1️⃣ Gutenberg
    628     if (wp?.data?.select("core/editor")) {
    629       const gutenbergTitle = wp.data
    630         .select("core/editor")
    631         .getEditedPostAttribute("title")
    632         ?.trim();
    633       if (gutenbergTitle) {
    634         return gutenbergTitle;
    635       }
    636     }
    637 
    638     // 2️⃣ Classic Editor
    639     const classicTitleInput = document.getElementById("title");
    640     if (classicTitleInput && classicTitleInput.value?.trim()) {
    641       return classicTitleInput.value.trim();
    642     }
    643 
    644     const metaInput = document.querySelector('input[name="post_title"]');
    645     if (metaInput && metaInput.value?.trim()) {
    646       return metaInput.value.trim();
    647     }
    648 
    649     return null;
    650   }
    651 
    652   function toggleSeoTitleButton() {
    653     const keyword = keywordInput?.value?.trim();
    654     if (keyword && keyword.length > 0) {
    655       seoTitleBtn.disabled = false;
    656       seoTitleBtn.style.opacity = "1";
    657       seoTitleBtn.style.cursor = "pointer";
    658     } else {
    659       seoTitleBtn.disabled = true;
    660       seoTitleBtn.style.opacity = "0.5";
    661       seoTitleBtn.style.cursor = "not-allowed";
    662     }
    663   }
    664 
    665   if (seoTitleBtn && keywordInput) {
    666     toggleSeoTitleButton();
     303  if (seoTitleBtn && keywordInput && seoTitleResultBox && seoTitleLoadingBox) {
     304    function toggleSeoTitleButton() {
     305      const hasKeyword = keywordInput.value.trim().length > 0;
     306      seoTitleBtn.disabled = !hasKeyword;
     307      seoTitleBtn.style.opacity = hasKeyword ? "1" : "0.5";
     308      seoTitleBtn.style.cursor = hasKeyword ? "pointer" : "not-allowed";
     309    }
     310
    667311    keywordInput.addEventListener("input", toggleSeoTitleButton);
    668312    keywordInput.addEventListener("paste", () =>
    669       setTimeout(toggleSeoTitleButton, 0)
     313      setTimeout(toggleSeoTitleButton, 100)
    670314    );
    671   }
    672 
    673   if (seoTitleBtn) {
    674     seoTitleBtn.addEventListener("click", async function () {
    675       const keyword = keywordInput?.value?.trim();
     315    toggleSeoTitleButton();
     316
     317    function getSeoTitleMessage(key) {
     318      const translations = {
     319        generating: "Generating SEO title, please wait...",
     320        success_generated: "SEO title generated and saved successfully!",
     321        page_refreshing: "Page is refreshing to display changes...",
     322        err_keyword_empty:
     323          "Please enter a keyword before generating SEO title.",
     324        err_server_error: "Server error occurred while generating SEO title.",
     325        err_save_failed: "Failed to save SEO title to Yoast.",
     326        err_yoast_missing: "Yoast SEO plugin is not active.",
     327        err_daily_limit_exceeded:
     328          "Daily AI generation limit reached. Upgrade or try tomorrow.",
     329      };
     330      return wp.i18n.__(translations[key] || key, "aicontify");
     331    }
     332
     333    seoTitleBtn.addEventListener("click", async function (e) {
     334      e.preventDefault();
     335
     336      const postId = seoTitleBtn.getAttribute("data-postid");
     337      const keyword = keywordInput.value.trim();
     338
     339      seoTitleResultBox.innerHTML = "";
     340      seoTitleLoadingBox.style.display = "block";
     341      seoTitleResultBox.innerHTML = `<span style='color: #0073aa;'>${getSeoTitleMessage(
     342        "generating"
     343      )}</span>`;
     344
    676345      if (!keyword) {
    677346        seoTitleResultBox.innerHTML = `<span class='error'>${getSeoTitleMessage(
    678347          "err_keyword_empty"
    679348        )}</span>`;
     349        seoTitleLoadingBox.style.display = "none";
    680350        return;
    681351      }
    682352
    683       const postId = seoTitleBtn.dataset.postid;
    684       if (!postId) {
    685         seoTitleResultBox.innerHTML = `<span class='error'>${getSeoTitleMessage(
    686           "err_post_id_invalid"
    687         )}</span>`;
    688         return;
    689       }
    690 
    691       if (
    692         !window.aicontApiSettings ||
    693         !aicontApiSettings.root ||
    694         !aicontApiSettings.nonce
    695       ) {
    696         seoTitleResultBox.innerHTML = `<span class='error'>${getSeoTitleMessage(
    697           "err_wp_settings_missing"
    698         )}</span>`;
    699         return;
    700       }
    701 
    702       seoTitleBtn.disabled = true;
    703       seoTitleBtn.textContent = wp.i18n.__(
    704         "Generating SEO title...",
    705         "aicontify"
    706       );
    707       seoTitleResultBox.innerHTML = "";
    708       seoTitleLoadingBox.style.display = "block";
    709 
    710353      try {
    711         const postTitle = getPostTitle();
    712         if (!postTitle) {
    713           throw new Error("err_post_title_missing");
    714         }
    715 
    716         const singlePostPrompt =
    717           document
    718             .getElementById("aicont_singlepost_seo_title_prompt_custom")
    719             ?.value?.trim() || "";
    720         const globalPrompt = aicontApiSettings.seo_title_prompt_custom || "";
    721 
    722         const customPrompt = singlePostPrompt || globalPrompt || "";
    723 
    724         const requestData = {
    725           keyword,
    726           post_id: parseInt(postId),
    727           post_title: postTitle,
    728           api_key: aicontApiSettings.api_key || "",
    729           site_language: aicontApiSettings.site_language,
    730           site_title: aicontApiSettings.site_title,
    731           prompt: customPrompt,
    732         };
    733 
    734         const response = await fetch(
    735           aicontApiSettings.root + "aicont/v1/generate_seo_titlee",
    736           {
    737             method: "POST",
    738             headers: {
    739               "Content-Type": "application/json",
    740               "X-WP-Nonce": aicontApiSettings.nonce,
    741             },
    742             body: JSON.stringify(requestData),
    743           }
     354        const customPromptEl = document.getElementById(
     355          "aicont_singlepost_seo_title_prompt_custom"
    744356        );
     357        const customPrompt = customPromptEl ? customPromptEl.value.trim() : "";
     358
     359        const formData = new URLSearchParams();
     360        formData.append("action", "aicont_generate_and_save_seo_title");
     361        formData.append("nonce", aicontify_ajax.nonce);
     362        formData.append("post_id", postId);
     363        formData.append("keyword", keyword);
     364        formData.append("custom_prompt", customPrompt);
     365
     366        const response = await fetch(aicontify_ajax.ajax_url, {
     367          method: "POST",
     368          credentials: "same-origin",
     369          headers: { "Content-Type": "application/x-www-form-urlencoded" },
     370          body: formData,
     371        });
    745372
    746373        const data = await response.json();
    747374
    748         if (!response.ok) {
    749           const code = data.error_code || "err_server_error";
     375        if (!response.ok || !data.success) {
     376          const code = data.data?.code || "err_server_error";
    750377          throw new Error(code);
    751378        }
    752379
    753         if (data.success && data.seo_title) {
    754           seoTitleResultBox.innerHTML = `
     380        const title = data.seo_title
     381          ? `: <strong>${data.seo_title}</strong>`
     382          : "";
     383        seoTitleResultBox.innerHTML = `
    755384        <span class='success'>${getSeoTitleMessage(
    756           "success_seo_title_generated"
    757         )}: <strong>${data.seo_title}</strong></span><br>
    758         <span style='color: #0073aa;'>${wp.i18n.__(
    759           "Page is refreshing to display changes...",
    760           "aicontify"
     385          "success_generated"
     386        )}${title}</span><br>
     387        <span style='color: #0073aa;'>${getSeoTitleMessage(
     388          "page_refreshing"
    761389        )}</span>
    762390      `;
    763           setTimeout(() => location.reload(), 1500);
    764         } else {
    765           throw new Error(data.error_code || "err_invalid_response");
    766         }
    767       } catch (err) {
    768         const code = err.message.startsWith("err_")
    769           ? err.message
    770           : "err_server_error";
     391
     392        setTimeout(() => location.reload(), 1500);
     393      } catch (error) {
     394        console.error("AiContify SEO Title Error:", error);
     395        const code = error.message;
     396        const msg =
     397          {
     398            err_keyword_empty: "err_keyword_empty",
     399            err_save_failed: "err_save_failed",
     400            err_yoast_missing: "err_yoast_missing",
     401            err_daily_limit_exceeded: "err_daily_limit_exceeded",
     402          }[code] || "err_server_error";
     403
    771404        seoTitleResultBox.innerHTML = `<span class='error'>${getSeoTitleMessage(
    772           code
     405          msg
    773406        )}</span>`;
    774407      } finally {
    775         toggleSeoTitleButton();
    776         seoTitleBtn.textContent = wp.i18n.__("Generate SEO Title", "aicontify");
    777408        seoTitleLoadingBox.style.display = "none";
    778409      }
     
    780411  }
    781412
    782   // --- Meta Description Generator ---
     413  // --- Meta Description Generator (PHP-Only + AJAX) ---
    783414  const metaDescBtn = document.getElementById(
    784415    "aicont-generate-meta-description-btn"
     
    791422  );
    792423
    793   // --- Message Translator for Meta Description ---
    794   function getMetaDescMessage(code) {
    795     const messages = {
    796       // Errors
    797       err_keyword_empty:
    798         "Please enter a keyword in the field above the tabs before generating meta description.",
    799       err_post_id_invalid: "Invalid post ID.",
    800       err_wp_settings_missing:
    801         "WordPress API settings are missing or incomplete. Please check plugin configuration.",
    802       err_connection_failed:
    803         "Failed to connect to the server. Please check your internet.",
    804       err_server_error: "Server error occurred.",
    805       err_invalid_response: "Invalid response from server.",
    806       err_meta_empty: "Generated meta description is empty.",
    807       err_save_failed: "Failed to save meta description.",
    808       err_post_title_missing: "Post title is missing.",
    809       err_daily_limit_exceeded:
    810         "Daily AI post limit reached. Upgrade to Pro or try tomorrow.",
    811 
    812       // Success
    813       success_meta_generated:
    814         "Meta description generated and saved successfully!",
    815     };
    816 
    817     return wp.i18n.__(messages[code] || "Unknown error occurred.", "aicontify");
    818   }
    819 
    820   function getPostTitle() {
    821     if (wp?.data?.select("core/editor")) {
    822       const gutenbergTitle = wp.data
    823         .select("core/editor")
    824         .getEditedPostAttribute("title")
    825         ?.trim();
    826       if (gutenbergTitle) {
    827         return gutenbergTitle;
    828       }
    829     }
    830 
    831     const classicTitleInput = document.getElementById("title");
    832     if (classicTitleInput && classicTitleInput.value?.trim()) {
    833       return classicTitleInput.value.trim();
    834     }
    835 
    836     const metaInput = document.querySelector('input[name="post_title"]');
    837     if (metaInput && metaInput.value?.trim()) {
    838       return metaInput.value.trim();
    839     }
    840 
    841     return null;
    842   }
    843 
    844   function toggleMetaDescButton() {
    845     const keyword = keywordInput?.value?.trim();
    846     if (keyword && keyword.length > 0) {
    847       metaDescBtn.disabled = false;
    848       metaDescBtn.style.opacity = "1";
    849       metaDescBtn.style.cursor = "pointer";
    850     } else {
    851       metaDescBtn.disabled = true;
    852       metaDescBtn.style.opacity = "0.5";
    853       metaDescBtn.style.cursor = "not-allowed";
    854     }
    855   }
    856 
    857   if (metaDescBtn && keywordInput) {
    858     toggleMetaDescButton();
     424  if (metaDescBtn && keywordInput && metaDescResultBox && metaDescLoadingBox) {
     425    function toggleMetaDescButton() {
     426      const hasKeyword = keywordInput.value.trim().length > 0;
     427      metaDescBtn.disabled = !hasKeyword;
     428      metaDescBtn.style.opacity = hasKeyword ? "1" : "0.5";
     429      metaDescBtn.style.cursor = hasKeyword ? "pointer" : "not-allowed";
     430    }
     431
    859432    keywordInput.addEventListener("input", toggleMetaDescButton);
    860433    keywordInput.addEventListener("paste", () =>
    861       setTimeout(toggleMetaDescButton, 0)
     434      setTimeout(toggleMetaDescButton, 100)
    862435    );
    863   }
    864 
    865   if (metaDescBtn) {
    866     metaDescBtn.addEventListener("click", async function () {
    867       const keyword = keywordInput?.value?.trim();
     436    toggleMetaDescButton();
     437
     438    function getMetaDescMessage(key) {
     439      const translations = {
     440        generating: "Generating meta description, please wait...",
     441        success_generated: "Meta description generated and saved successfully!",
     442        page_refreshing: "Page is refreshing to display changes...",
     443        err_keyword_empty:
     444          "Please enter a keyword before generating meta description.",
     445        err_server_error:
     446          "Server error occurred while generating meta description.",
     447        err_save_failed: "Failed to save meta description to Yoast.",
     448        err_yoast_missing: "Yoast SEO plugin is not active.",
     449        err_daily_limit_exceeded:
     450          "Daily AI generation limit reached. Upgrade or try tomorrow.",
     451      };
     452      return wp.i18n.__(translations[key] || key, "aicontify");
     453    }
     454
     455    metaDescBtn.addEventListener("click", async function (e) {
     456      e.preventDefault();
     457
     458      const postId = metaDescBtn.getAttribute("data-postid");
     459      const keyword = keywordInput.value.trim();
     460
     461      metaDescResultBox.innerHTML = "";
     462      metaDescLoadingBox.style.display = "block";
     463      metaDescResultBox.innerHTML = `<span style='color: #0073aa;'>${getMetaDescMessage(
     464        "generating"
     465      )}</span>`;
     466
    868467      if (!keyword) {
    869468        metaDescResultBox.innerHTML = `<span class='error'>${getMetaDescMessage(
    870469          "err_keyword_empty"
    871470        )}</span>`;
     471        metaDescLoadingBox.style.display = "none";
    872472        return;
    873473      }
    874474
    875       const postId = metaDescBtn.dataset.postid;
    876       if (!postId) {
    877         metaDescResultBox.innerHTML = `<span class='error'>${getMetaDescMessage(
    878           "err_post_id_invalid"
    879         )}</span>`;
    880         return;
    881       }
    882 
    883       if (
    884         !window.aicontApiSettings ||
    885         !aicontApiSettings.root ||
    886         !aicontApiSettings.nonce
    887       ) {
    888         metaDescResultBox.innerHTML = `<span class='error'>${getMetaDescMessage(
    889           "err_wp_settings_missing"
    890         )}</span>`;
    891         return;
    892       }
    893 
    894       metaDescBtn.disabled = true;
    895       metaDescBtn.textContent = wp.i18n.__(
    896         "Generating meta description...",
    897         "aicontify"
    898       );
    899       metaDescResultBox.innerHTML = "";
    900       metaDescLoadingBox.style.display = "block";
    901 
    902475      try {
    903         const postTitle = getPostTitle();
    904         if (!postTitle) throw new Error("err_post_title_missing");
    905 
    906         const singlePostPrompt =
    907           document
    908             .getElementById("aicont_singlepost_seo_description_prompt_custom")
    909             ?.value?.trim() || "";
    910         const globalPrompt = aicontApiSettings.seo_meta_prompt_custom || "";
    911 
    912         const customPrompt = singlePostPrompt || globalPrompt || "";
    913 
    914         const requestData = {
    915           keyword,
    916           post_id: parseInt(postId),
    917           post_title: postTitle,
    918           api_key: aicontApiSettings.api_key || "",
    919           site_language: aicontApiSettings.site_language,
    920           site_title: aicontApiSettings.site_title,
    921           prompt: customPrompt,
    922         };
    923 
    924         const response = await fetch(
    925           aicontApiSettings.root + "aicont/v1/generate_meta_descriptionn",
    926           {
    927             method: "POST",
    928             headers: {
    929               "Content-Type": "application/json",
    930               "X-WP-Nonce": aicontApiSettings.nonce,
    931             },
    932             body: JSON.stringify(requestData),
    933           }
     476        const customPromptEl = document.getElementById(
     477          "aicont_singlepost_seo_description_prompt_custom"
    934478        );
     479        const customPrompt = customPromptEl ? customPromptEl.value.trim() : "";
     480
     481        const formData = new URLSearchParams();
     482        formData.append("action", "aicont_generate_and_save_meta_description");
     483        formData.append("nonce", aicontify_ajax.nonce);
     484        formData.append("post_id", postId);
     485        formData.append("keyword", keyword);
     486        formData.append("custom_prompt", customPrompt);
     487
     488        const response = await fetch(aicontify_ajax.ajax_url, {
     489          method: "POST",
     490          credentials: "same-origin",
     491          headers: { "Content-Type": "application/x-www-form-urlencoded" },
     492          body: formData,
     493        });
    935494
    936495        const data = await response.json();
    937496
    938         if (!response.ok) {
    939           const code = data.error_code || "err_server_error";
     497        if (!response.ok || !data.success) {
     498          const code = data.data?.code || "err_server_error";
    940499          throw new Error(code);
    941500        }
    942501
    943         if (data.success && data.meta_description) {
    944           metaDescResultBox.innerHTML = `
     502        const desc = data.meta_description
     503          ? `: <strong>${data.meta_description}</strong>`
     504          : "";
     505        metaDescResultBox.innerHTML = `
    945506        <span class='success'>${getMetaDescMessage(
    946           "success_meta_generated"
    947         )}: <strong>${data.meta_description}</strong></span><br>
    948         <span style='color: #0073aa;'>${wp.i18n.__(
    949           "Page is refreshing to display changes...",
    950           "aicontify"
     507          "success_generated"
     508        )}${desc}</span><br>
     509        <span style='color: #0073aa;'>${getMetaDescMessage(
     510          "page_refreshing"
    951511        )}</span>
    952512      `;
    953           setTimeout(() => location.reload(), 1500);
    954         } else {
    955           throw new Error(data.error_code || "err_invalid_response");
    956         }
    957       } catch (err) {
    958         const code = err.message.startsWith("err_")
    959           ? err.message
    960           : "err_server_error";
     513
     514        setTimeout(() => location.reload(), 1500);
     515      } catch (error) {
     516        console.error("AiContify Meta Description Error:", error);
     517        const code = error.message;
     518        const msg =
     519          {
     520            err_keyword_empty: "err_keyword_empty",
     521            err_save_failed: "err_save_failed",
     522            err_yoast_missing: "err_yoast_missing",
     523            err_daily_limit_exceeded: "err_daily_limit_exceeded",
     524          }[code] || "err_server_error";
     525
    961526        metaDescResultBox.innerHTML = `<span class='error'>${getMetaDescMessage(
    962           code
     527          msg
    963528        )}</span>`;
    964529      } finally {
    965         toggleMetaDescButton();
    966         metaDescBtn.textContent = wp.i18n.__(
    967           "Generate Meta Description",
    968           "aicontify"
    969         );
    970530        metaDescLoadingBox.style.display = "none";
    971531      }
  • aicontify/trunk/readme.txt

    r3397131 r3398370  
    44Requires at least: 5.0
    55Tested up to: 6.8
    6 Stable tag: 4.0.2
     6Stable tag: 5.0.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    121121== Changelog ==
    122122
     123= 5.0.0 = November 19, 2025
     124Rest API optimization
     125
    123126= 4.0.0 = November 17, 2025
    124127Added a custom prompt field inside post editor for more flexible content generation
  • aicontify/trunk/seoDescription.php

    r3397005 r3398370  
    22if (!defined('ABSPATH')) exit;
    33
    4 // -------------------- REST API: Meta Description Generator (Client) --------------------
    5 add_action('rest_api_init', function() {
    6     register_rest_route('aicont/v1', '/generate_meta_descriptionn', [
    7         'methods' => 'POST',
    8         'callback' => 'aicont_generate_meta_description_api_callback',
    9         'permission_callback' => function() {
    10             return current_user_can('edit_posts');
    11         },
    12         'args' => [
    13             'keyword'       => ['required' => true,  'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    14             'post_id'       => ['required' => true,  'type' => 'integer', 'sanitize_callback' => 'absint'],
    15             'post_title'    => ['required' => true,  'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    16             'api_key'       => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    17             'site_language' => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    18             'site_title'    => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    19             'prompt'        => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_textarea_field'],
    20         ]
    21     ]);
    22 });
     4add_action('wp_ajax_aicont_generate_and_save_meta_description', 'aicont_generate_and_save_meta_description_handler');
    235
    24 function aicont_generate_meta_description_api_callback($request) {
    25     global $aicont_api_key, $aicont_site_language, $aicont_site_title;
     6function aicont_generate_and_save_meta_description_handler() {
     7    check_ajax_referer('aicont_nonce', 'nonce');
    268
    27     $keyword       = $request->get_param('keyword');
    28     $post_id       = $request->get_param('post_id');
    29     $post_title    = $request->get_param('post_title');
    30     $api_key       = $request->get_param('api_key') ?: $aicont_api_key;
    31     $site_language = $request->get_param('site_language') ?: $aicont_site_language;
    32     $site_title    = $request->get_param('site_title') ?: $aicont_site_title;
    33     $prompt        = $request->get_param('prompt');
     9    $post_id = absint($_POST['post_id'] ?? 0);
     10    $keyword = sanitize_text_field($_POST['keyword'] ?? '');
     11    $custom_prompt = sanitize_textarea_field($_POST['custom_prompt'] ?? '');
    3412
    35     if (empty($keyword)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_keyword_empty'], 400);
    36     if (empty($post_id)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_id_invalid'], 400);
    37     if (empty($post_title)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_title_missing'], 400);
    38 
    39     $post = get_post($post_id);
    40     if (!$post) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_not_found'], 404);
    41 
    42     // Priority: custom_prompt from request > post meta > global option
    43     if (empty($prompt)) {
    44         $prompt = get_post_meta($post_id, 'aicont_singlepost_seo_description_prompt_custom', true);
     13    if (!$post_id || !get_post($post_id)) {
     14        wp_send_json_error(['code' => 'err_post_id_invalid']);
    4515    }
    46     if (empty($prompt)) {
    47         $prompt = get_option('aicont_plugin_seo_description_prompt_custom', '');
     16    if (empty($keyword)) {
     17        wp_send_json_error(['code' => 'err_keyword_empty']);
    4818    }
    4919
     20    $post_title = get_the_title($post_id);
     21    if (empty($post_title)) {
     22        wp_send_json_error(['code' => 'err_post_title_missing']);
     23    }
     24
     25    if (empty($custom_prompt)) {
     26        $custom_prompt = get_post_meta($post_id, 'aicont_singlepost_seo_description_prompt_custom', true);
     27    }
     28    if (empty($custom_prompt)) {
     29        $custom_prompt = get_option('aicont_plugin_seo_meta_prompt_custom', '');
     30    }
     31
     32    $active_license = get_option('aicont_active_license', '');
     33
     34    $result = aicont_call_webtinus_meta_description([
     35        'keyword'     => $keyword,
     36        'post_id'     => $post_id,
     37        'post_title'  => $post_title,
     38        'prompt'      => $custom_prompt,
     39        'api_key'     => $active_license
     40    ]);
     41
     42    if (is_wp_error($result) || empty($result['meta_description'])) {
     43        wp_send_json_error(['code' => $result['error_code'] ?? 'err_meta_empty']);
     44    }
     45
     46    $meta_description = sanitize_textarea_field(trim($result['meta_description']));
     47
     48    if (!class_exists('WPSEO_Meta')) {
     49        wp_send_json_error(['code' => 'err_yoast_missing']);
     50    }
     51
     52    $saved = update_post_meta($post_id, '_yoast_wpseo_metadesc', $meta_description);
     53    if ($saved === false) {
     54        wp_send_json_error(['code' => 'err_save_failed']);
     55    }
     56
     57    wp_update_post([
     58        'ID' => $post_id,
     59        'post_modified'     => current_time('mysql'),
     60        'post_modified_gmt' => current_time('mysql', 1)
     61    ]);
     62
     63    wp_send_json_success([
     64        'message' => 'success_meta_generated',
     65        'meta_description' => $meta_description
     66    ]);
     67}
     68
     69function aicont_call_webtinus_meta_description($args) {
    5070    $client_site_url = home_url('', 'https');
    51     if (empty($client_site_url)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_site_url'], 500);
     71    $server_url = 'https://webtinus.com/wp-json/aicont/v1/generate_meta_description1';
    5272
    53     $server_url = 'https://webtinus.com/wp-json/aicont/v1/generate_meta_descriptionn';
    5473    $body = wp_json_encode([
    55         'keyword'        => $keyword,
    56         'post_id'        => $post_id,
    57         'post_title'     => $post_title,
    58         'prompt'         => $prompt,
    59         'client_site_url'=> $client_site_url,
    60         'site_title'     => $site_title,
    61         'site_language'  => $site_language,
    62         'api_key'        => $api_key
     74        'keyword'         => $args['keyword'],
     75        'post_id'         => $args['post_id'],
     76        'post_title'      => $args['post_title'],
     77        'prompt'          => $args['prompt'],
     78        'api_key'         => $args['api_key'] ?? '',
     79        'client_site_url' => $client_site_url,
     80        'site_title'      => get_bloginfo('name'),
     81        'site_language'   => get_locale()
    6382    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    6483
     
    6685        'headers' => ['Content-Type' => 'application/json', 'X-Client-Site' => $client_site_url],
    6786        'body'    => $body,
    68         'timeout' => 60,
    69         'sslverify' => true
     87        'timeout' => 90
    7088    ]);
    7189
    7290    if (is_wp_error($response)) {
    73         return new WP_REST_Response(['success' => false, 'error_code' => 'err_connection_failed'], 500);
     91        return ['error_code' => 'err_connection_failed'];
    7492    }
    7593
    76     $status_code = wp_remote_retrieve_response_code($response);
    77     $body = wp_remote_retrieve_body($response);
    78     $data = json_decode($body, true);
     94    $data = json_decode(wp_remote_retrieve_body($response), true);
    7995
    80     if ($status_code !== 200 || !isset($data['success'])) {
    81         return new WP_REST_Response(['success' => false, 'error_code' => $data['error_code'] ?? 'err_server_error'], $status_code);
     96    if (empty($data['success']) || empty($data['meta_description'])) {
     97        return ['error_code' => $data['error_code'] ?? 'err_meta_empty'];
    8298    }
    8399
    84     if (!$data['success'] || empty($data['meta_description'])) {
    85         return new WP_REST_Response(['success' => false, 'error_code' => $data['error_code'] ?? 'err_meta_empty'], 500);
    86     }
    87 
    88     $meta_description = sanitize_textarea_field($data['meta_description']);
    89 
    90     if (!class_exists('WPSEO_Meta')) {
    91         return new WP_REST_Response(['success' => false, 'error_code' => 'err_yoast_missing'], 400);
    92     }
    93 
    94     $meta_updated = update_post_meta($post_id, '_yoast_wpseo_metadesc', $meta_description);
    95     if ($meta_updated === false) {
    96         return new WP_REST_Response(['success' => false, 'error_code' => 'err_save_failed'], 500);
    97     }
    98 
    99     $post_data = [
    100         'ID' => $post_id,
    101         'post_modified' => current_time('mysql'),
    102         'post_modified_gmt' => current_time('mysql', 1),
    103         'post_status' => $post->post_status
    104     ];
    105 
    106     $post_updated = wp_update_post($post_data, true);
    107     if (is_wp_error($post_updated)) {
    108         return new WP_REST_Response(['success' => false, 'error_code' => 'err_save_failed'], 500);
    109     }
    110 
    111     return new WP_REST_Response([
    112         'success' => true,
    113         'message_code' => 'success_meta_generated',
    114         'meta_description' => $meta_description
    115     ], 200);
     100    return ['meta_description' => trim($data['meta_description'])];
    116101}
  • aicontify/trunk/seoTitle.php

    r3397005 r3398370  
    22if (!defined('ABSPATH')) exit;
    33
    4 // -------------------- REST API: SEO Title Generator (Client) --------------------
    5 add_action('rest_api_init', function() {
    6     register_rest_route('aicont/v1', '/generate_seo_titlee', [
    7         'methods' => 'POST',
    8         'callback' => 'aicont_generate_seo_title_api_callback',
    9         'permission_callback' => function() {
    10             return current_user_can('edit_posts');
    11         },
    12         'args' => [
    13             'keyword'       => ['required' => true,  'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    14             'post_id'       => ['required' => true,  'type' => 'integer', 'sanitize_callback' => 'absint'],
    15             'post_title'    => ['required' => true,  'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    16             'model_id'      => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    17             'api_key'       => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    18             'site_language' => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    19             'site_title'    => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_text_field'],
    20             'prompt'        => ['required' => false, 'type' => 'string',  'sanitize_callback' => 'sanitize_textarea_field'],
    21         ]
    22     ]);
    23 });
     4add_action('wp_ajax_aicont_generate_and_save_seo_title', 'aicont_generate_and_save_seo_title_handler');
    245
    25 function aicont_generate_seo_title_api_callback($request) {
    26     global $aicont_model_id, $aicont_api_key, $aicont_site_language, $aicont_site_title;
     6function aicont_generate_and_save_seo_title_handler() {
     7    check_ajax_referer('aicont_nonce', 'nonce');
    278
    28     $keyword       = $request->get_param('keyword');
    29     $post_id       = $request->get_param('post_id');
    30     $post_title    = $request->get_param('post_title');
    31     $model_id      = $request->get_param('model_id') ?: $aicont_model_id;
    32     $api_key       = $request->get_param('api_key') ?: $aicont_api_key;
    33     $site_language = $request->get_param('site_language') ?: $aicont_site_language;
    34     $site_title    = $request->get_param('site_title') ?: $aicont_site_title;
    35     $prompt        = $request->get_param('prompt');
     9    $post_id = absint($_POST['post_id'] ?? 0);
     10    $keyword = sanitize_text_field($_POST['keyword'] ?? '');
     11    $custom_prompt = sanitize_textarea_field($_POST['custom_prompt'] ?? '');
    3612
    37     // Validation
    38     if (empty($keyword)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_keyword_empty'], 400);
    39     if (empty($post_id)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_id_invalid'], 400);
    40     if (empty($post_title)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_title_missing'], 400);
    41 
    42     $post = get_post($post_id);
    43     if (!$post) return new WP_REST_Response(['success' => false, 'error_code' => 'err_post_not_found'], 404);
    44 
    45     // Priority: custom_prompt from request > post meta > global option
    46     if (empty($prompt)) {
    47         $prompt = get_post_meta($post_id, 'aicont_singlepost_seo_title_prompt_custom', true);
     13    if (!$post_id || !get_post($post_id)) {
     14        wp_send_json_error(['code' => 'err_post_id_invalid']);
    4815    }
    49     if (empty($prompt)) {
    50         $prompt = get_option('aicont_plugin_seo_title_prompt_custom', '');
     16    if (empty($keyword)) {
     17        wp_send_json_error(['code' => 'err_keyword_empty']);
    5118    }
    5219
     20    $post_title = get_the_title($post_id);
     21    if (empty($post_title)) {
     22        wp_send_json_error(['code' => 'err_post_title_missing']);
     23    }
     24
     25    if (empty($custom_prompt)) {
     26        $custom_prompt = get_post_meta($post_id, 'aicont_singlepost_seo_title_prompt_custom', true);
     27    }
     28    if (empty($custom_prompt)) {
     29        $custom_prompt = get_option('aicont_plugin_seo_title_prompt_custom', '');
     30    }
     31
     32    $active_license = get_option('aicont_active_license', '');
     33
     34    $result = aicont_call_webtinus_seo_title([
     35        'keyword'     => $keyword,
     36        'post_id'     => $post_id,
     37        'post_title'  => $post_title,
     38        'prompt'      => $custom_prompt,
     39        'api_key'     => $active_license
     40    ]);
     41
     42    if (is_wp_error($result) || empty($result['seo_title'])) {
     43        wp_send_json_error(['code' => $result['error_code'] ?? 'err_title_empty']);
     44    }
     45
     46    $seo_title = wp_strip_all_tags(sanitize_text_field($result['seo_title']));
     47
     48    if (!class_exists('WPSEO_Meta')) {
     49        wp_send_json_error(['code' => 'err_yoast_missing']);
     50    }
     51
     52    $saved = update_post_meta($post_id, '_yoast_wpseo_title', $seo_title);
     53    if ($saved === false) {
     54        wp_send_json_error(['code' => 'err_save_failed']);
     55    }
     56
     57    wp_update_post([
     58        'ID' => $post_id,
     59        'post_modified'     => current_time('mysql'),
     60        'post_modified_gmt' => current_time('mysql', 1)
     61    ]);
     62
     63    wp_send_json_success([
     64        'message' => 'success_seo_title_generated',
     65        'seo_title' => $seo_title
     66    ]);
     67}
     68
     69function aicont_call_webtinus_seo_title($args) {
    5370    $client_site_url = home_url('', 'https');
    54     if (empty($client_site_url)) return new WP_REST_Response(['success' => false, 'error_code' => 'err_site_url'], 500);
     71    $server_url = 'https://webtinus.com/wp-json/aicont/v1/generate_seo_title1';
    5572
    56     // Send request to central server
    57     $server_url = 'https://webtinus.com/wp-json/aicont/v1/generate_seo_titlee';
    5873    $body = wp_json_encode([
    59         'keyword'         => $keyword,
    60         'post_id'         => $post_id,
    61         'post_title'      => $post_title,
    62         'prompt'          => $prompt,
     74        'keyword'         => $args['keyword'],
     75        'post_id'         => $args['post_id'],
     76        'post_title'      => $args['post_title'],
     77        'prompt'          => $args['prompt'],
     78        'api_key'         => $args['api_key'] ?? '',
    6379        'client_site_url' => $client_site_url,
    64         'site_title'      => $site_title,
    65         'site_language'   => $site_language,
    66         'model_id'        => $model_id,
    67         'api_key'         => $api_key
     80        'site_title'      => get_bloginfo('name'),
     81        'site_language'   => get_locale()
    6882    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    6983
    7084    $response = wp_remote_post($server_url, [
    71         'headers' => [
    72             'Content-Type' => 'application/json',
    73             'X-Client-Site' => $client_site_url
    74         ],
     85        'headers' => ['Content-Type' => 'application/json', 'X-Client-Site' => $client_site_url],
    7586        'body'    => $body,
    76         'timeout' => 60,
    77         'sslverify' => true
     87        'timeout' => 90
    7888    ]);
    7989
    8090    if (is_wp_error($response)) {
    81         return new WP_REST_Response(['success' => false, 'error_code' => 'err_connection_failed'], 500);
     91        return ['error_code' => 'err_connection_failed'];
    8292    }
    8393
    84     $status_code = wp_remote_retrieve_response_code($response);
    85     $body        = wp_remote_retrieve_body($response);
    86     $data        = json_decode($body, true);
     94    $data = json_decode(wp_remote_retrieve_body($response), true);
    8795
    88     if ($status_code !== 200 || !isset($data['success'])) {
    89         return new WP_REST_Response(['success' => false, 'error_code' => $data['error_code'] ?? 'err_server_error'], $status_code);
     96    if (empty($data['success']) || empty($data['seo_title'])) {
     97        return ['error_code' => $data['error_code'] ?? 'err_title_empty'];
    9098    }
    9199
    92     if (!$data['success'] || empty($data['seo_title'])) {
    93         return new WP_REST_Response(['success' => false, 'error_code' => $data['error_code'] ?? 'err_title_empty'], 500);
    94     }
    95 
    96     $seo_title = wp_strip_all_tags(sanitize_text_field($data['seo_title']));
    97 
    98     if (!class_exists('WPSEO_Meta')) {
    99         return new WP_REST_Response(['success' => false, 'error_code' => 'err_yoast_missing'], 400);
    100     }
    101 
    102     $meta_updated = update_post_meta($post_id, '_yoast_wpseo_title', $seo_title);
    103     if ($meta_updated === false) {
    104         return new WP_REST_Response(['success' => false, 'error_code' => 'err_save_failed'], 500);
    105     }
    106 
    107     $post_data = [
    108         'ID'                => $post_id,
    109         'post_modified'     => current_time('mysql'),
    110         'post_modified_gmt' => current_time('mysql', 1),
    111         'post_status'       => $post->post_status
    112     ];
    113 
    114     $post_updated = wp_update_post($post_data, true);
    115     if (is_wp_error($post_updated)) {
    116         return new WP_REST_Response(['success' => false, 'error_code' => 'err_save_failed'], 500);
    117     }
    118 
    119     return new WP_REST_Response([
    120         'success'    => true,
    121         'message_code'=> 'success_seo_title_generated',
    122         'seo_title'  => $seo_title
    123     ], 200);
     100    return ['seo_title' => trim($data['seo_title'])];
    124101}
Note: See TracChangeset for help on using the changeset viewer.