Plugin Directory

Changeset 3387025


Ignore:
Timestamp:
10/30/2025 11:29:07 AM (5 months ago)
Author:
purposego
Message:

Release version 1.2.0

Location:
clearpost-simple-ai-auto-post
Files:
18 edited
26 copied

Legend:

Unmodified
Added
Removed
  • clearpost-simple-ai-auto-post/tags/1.2.0/assets/css/onboarding.css

    r3357104 r3387025  
    2929
    3030.saiap-onboarding-container {
    31     padding: 24px;
     31    padding: 18px; /* reduced ~25% */
    3232}
    3333
    3434/* Header section */
    3535.saiap-onboarding-header {
    36     margin-bottom: 20px;
     36    margin-bottom: 14px; /* reduced spacing */
    3737    text-align: center;
    3838}
     
    5858    align-items: center;
    5959    gap: 16px;
    60     margin-bottom: 24px;
    61     padding: 0 20px;
     60    margin-bottom: 16px; /* reduced spacing */
     61    padding: 0 14px; /* reduced side padding */
    6262}
    6363
     
    109109    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    110110    gap: 20px;
    111     margin-bottom: 20px;
     111    margin-bottom: 14px; /* reduced spacing */
    112112}
    113113
     
    117117    align-items: flex-start;
    118118    gap: 12px;
    119     padding: 16px;
     119    padding: 12px; /* reduced ~25% */
    120120    background: #ffffff;
    121121    border-radius: 8px;
     
    225225.saiap-onboarding-help {
    226226    text-align: center;
    227     padding-top: 16px;
     227    padding-top: 12px; /* reduced spacing */
    228228    border-top: 1px solid #e5e7eb;
    229229}
    230230
    231231.saiap-onboarding-help p {
    232     margin: 0;
    233     font-size: 14px;
    234     color: var(--text-secondary);
     232    margin: 4px 0; /* allow small separation for two lines */
     233    font-size: 14px;
     234    color: var(--text-secondary);
     235}
     236
     237/* Upsell footnote (muted) */
     238.saiap-onboarding-upsell-footnote {
     239    font-size: 12px;
     240    color: var(--text-secondary);
     241    opacity: 0.85;
    235242}
    236243
  • clearpost-simple-ai-auto-post/tags/1.2.0/assets/js/admin.js

    r3373464 r3387025  
    283283    }
    284284
     285    // Notification Settings save function
     286    window.saveNotificationSettings = function() {
     287        var form = document.getElementById('saiap-notifications-form');
     288        var btn = document.getElementById('saiap-save-notifications');
     289
     290        if (btn) {
     291            btn.disabled = true;
     292            btn.textContent = 'Saving...';
     293        }
     294
     295        var formData = new FormData(form);
     296        formData.append('action', 'saiap_save_notifications');
     297
     298        var nonceField = document.querySelector('input[name="saiap_notifications_nonce"]');
     299        if (nonceField) {
     300            formData.append('saiap_notifications_nonce', nonceField.value);
     301        }
     302
     303        $.ajax({
     304            url: ajaxurl,
     305            type: 'POST',
     306            data: formData,
     307            processData: false,
     308            contentType: false,
     309            success: function(response) {
     310                if (btn) {
     311                    btn.disabled = false;
     312                    btn.textContent = 'Save Notification Settings';
     313                }
     314
     315                var messageType = response.success ? 'success' : 'error';
     316                var message = response.success
     317                    ? (response.data?.message || 'Notification settings saved successfully.')
     318                    : (response.data || 'Error saving notification settings.');
     319
     320                showNotificationsMessage(messageType, message);
     321            },
     322            error: function() {
     323                if (btn) {
     324                    btn.disabled = false;
     325                    btn.textContent = 'Save Notification Settings';
     326                }
     327                showNotificationsMessage('error', 'Server error occurred. Please try again later.');
     328            }
     329        });
     330    };
     331
     332    function showNotificationsMessage(type, message) {
     333        var msgElem = document.getElementById('notifications-settings-message');
     334        if (msgElem) {
     335            msgElem.innerHTML = '<div class="notice notice-' + type + ' inline"><p>' + message + '</p></div>';
     336            msgElem.style.display = 'block';
     337
     338            setTimeout(function() {
     339                msgElem.style.display = 'none';
     340            }, 5000);
     341        }
     342    }
     343
    285344    // Tracking consent notice handlers
    286345    $('#saiap-tracking-allow').on('click', function() {
  • clearpost-simple-ai-auto-post/tags/1.2.0/assets/js/scheduler.js

    r3373464 r3387025  
    3939                            <div class="prompt-actions">
    4040                                <button class="button-secondary edit-prompt">${saiapAdmin.i18n.edit}</button>
    41                                 <button class="button-secondary regenerate-prompt">${saiapAdmin.i18n.regenerate}</button>
    4241                            </div>
    4342                        </div>
     
    101100        const noticeHtml = `
    102101            <div class="notice notice-info saiap-scheduler-premium-notice">
    103                 <p><strong>Post Scheduler - Premium Feature</strong></p>
    104                 <p>This feature allows you to schedule AI-generated posts up to 7 days in advance with automated content creation. It uses your site's context architecture saved in our premium cloud service.</p>
    105                 <p style="margin-top:8px;">Auto-generate 7 days of posts in your exact style.</p>
     102                <p><strong>Post Scheduler - Generate organic traffic on autopilot</strong></p>
     103                <p>Watch as ClearPost learns what your audience wants and schedules posts that get traffic while you sleep.</p>
     104                <p style="margin-top:8px;">Get found on Google. Get recommended by ChatGPT. On autopilot.</p>
    106105                <p>
    107                     <button class="button-primary premium-upgrade-btn">Upgrade to Premium</button>
    108                     <a class="button-link" href="https://clearpostplugin.com/#pricing" target="_blank" style="margin-left: 10px;">Learn more about Premium</a>
     106                    <a class="button-link" href="https://clearpostplugin.com/" target="_blank" style="margin-left: 10px;">Grow traffic automatically</a>
    109107                    <button class="button-secondary notice-dismiss-btn" style="margin-left: 10px;">Dismiss</button>
    110108                </p>
     
    117115        }
    118116
    119         // Handle upgrade button click
    120         $('.premium-upgrade-btn').on('click', function(e) {
    121             e.preventDefault();
    122             window.open('https://clearpostplugin.com/#pricing', '_blank');
    123         });
    124 
    125117        // Handle dismiss button
    126118        $('.notice-dismiss-btn').on('click', function(e) {
     
    145137    });
    146138
    147     // Event handler for regenerating a prompt
    148     schedulerGrid.on('click', '.regenerate-prompt', function () {
    149         const button = $(this);
    150         const card = button.closest('.prompt-card');
    151         const promptId = card.data('id');
    152 
    153         if (!promptId) {
    154             alert('Could not find prompt ID.');
    155             return;
    156         }
    157 
    158         // Add loading state
    159         card.css('opacity', '0.5');
    160         button.prop('disabled', true);
    161 
    162         $.ajax({
    163             url: saiapAdmin.ajaxurl,
    164             method: 'POST',
    165             data: {
    166                 action: 'saiap_regenerate_prompt',
    167                 nonce: saiapAdmin.schedulerNonce,
    168                 prompt_id: promptId
    169             },
    170             success: function (response) {
    171                 if (response.success) {
    172                     // Update the UI with the new prompt
    173                     card.find('.prompt-content p').text(response.data.prompt);
    174                 } else {
    175                     const errorMessage = getErrorMessage(response.data);
    176                     alert('Error regenerating prompt: ' + errorMessage);
    177                 }
    178             },
    179             error: function () {
    180                 alert('Server error while regenerating prompt.');
    181             },
    182             complete: function () {
    183                 // Remove loading state
    184                 card.css('opacity', '1');
    185                 button.prop('disabled', false);
    186             }
    187         });
    188     });
     139   
    189140
    190141    // Modal handling
  • clearpost-simple-ai-auto-post/tags/1.2.0/assets/js/site-context.js

    r3373464 r3387025  
    195195        const noticeHtml = `
    196196            <div class="notice notice-info saiap-site-context-premium-notice">
    197                 <p><strong>Site Context AI Agent - Premium Feature</strong></p>
    198                 <p>This AI agent analyzes your existing content to understand your unique voice, style, and topics. It creates custom AI instructions so new posts automatically match your established style, replacing hours of manual prompt engineering.</p>
     197                <p><strong>The Secret to Posts that Rank (and Sound Like You)</strong></p>
     198                <p>This AI agent analyzes your existing content to understand your style, your audience, and your goals. It creates a custom context architecture that ensures your new posts naturally rank and sound like you.</p>
    199199                <p>
    200                     <button class="button-primary premium-upgrade-btn">Upgrade to Premium</button>
     200                    <button class="button-primary premium-upgrade-btn">Grow organic traffic on autopilot</button>
    201201                    <button class="button-secondary notice-dismiss-btn" style="margin-left: 10px;">Dismiss</button>
    202202                </p>
     
    212212        $('.premium-upgrade-btn').on('click', function(e) {
    213213            e.preventDefault();
    214             window.open('https://clearpostplugin.com/#pricing', '_blank');
     214            window.open('https://clearpostplugin.com/', '_blank');
    215215        });
    216216
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/ai-requests.php

    r3373464 r3387025  
    4242    $messages[] = array( 'role' => 'user', 'content' => $user_message );
    4343
    44     // Make API call based on provider
    45     if ( $provider === 'openai' ) {
    46         $schema = array(
    47             'type'       => 'object',
    48             'properties' => array(
    49                 'chat_message'   => array(
    50                     'type'        => 'string',
    51                     'description' => 'Brief explanation of what you changed or a conversational response.',
    52                 ),
    53                 'has_edit'       => array(
    54                     'type'        => 'boolean',
    55                     'description' => 'Whether an edit was made to the content.',
    56                 ),
    57                 'edited_content' => array(
    58                     'type'        => 'string',
    59                     'description' => 'The complete edited content. If no edit is made, this should be an empty string.',
    60                 ),
    61             ),
    62             'required'   => array( 'chat_message', 'has_edit', 'edited_content' ),
    63             'additionalProperties' => false,
    64         );
    65 
    66         $response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array(
    67             'headers' => array(
    68                 'Authorization' => 'Bearer ' . $api_key,
    69                 'Content-Type' => 'application/json',
    70             ),
    71             'body' => wp_json_encode( array(
    72                 'model' => $model ?: 'gpt-5',
    73                 'messages' => $messages,
    74                 'tool_choice' => array( 'type' => 'function', 'function' => array( 'name' => 'json_responder') ),
    75                 'tools'       => array(
    76                     array(
    77                         'type' => 'function',
    78                         'function' => array(
    79                             'name' => 'json_responder',
    80                             'description' => 'Responds with a JSON object containing a chat message and edited content.',
    81                             'parameters' => $schema,
    82                             'strict' => true,
    83                         )
    84                     )
    85                 )
    86             ) ),
    87             'timeout' => 120,
    88         ) );
    89 
    90         if ( is_wp_error( $response ) ) {
    91             return $response;
    92         }
    93 
    94         $body = wp_remote_retrieve_body( $response );
    95         $data = json_decode( $body, true );
    96        
    97         if ( isset( $data['choices'][0]['message']['tool_calls'][0]['function']['arguments'] ) ) {
    98             return $data['choices'][0]['message']['tool_calls'][0]['function']['arguments'];
    99         }
    100 
    101         if ( isset( $data['error']['message'] ) ) {
    102             return new WP_Error( 'api_error', 'OpenAI API Error: ' . sanitize_text_field( $data['error']['message'] ) );
    103         }
    104        
    105         return new WP_Error( 'invalid_response', 'Invalid response from OpenAI API' );
     44    // Make API call based on provider
     45    if ( $provider === 'openai' ) {
     46        $schema = array(
     47            'type'       => 'object',
     48            'properties' => array(
     49                'chat_message'   => array(
     50                    'type'        => 'string',
     51                    'description' => 'Brief explanation of what you changed or a conversational response.',
     52                ),
     53                'has_edit'       => array(
     54                    'type'        => 'boolean',
     55                    'description' => 'Whether an edit was made to the content.',
     56                ),
     57                'edited_content' => array(
     58                    'type'        => 'string',
     59                    'description' => 'The complete edited content. If no edit is made, this should be an empty string.',
     60                ),
     61            ),
     62            'required'   => array( 'chat_message', 'has_edit', 'edited_content' ),
     63            'additionalProperties' => false,
     64        );
     65
     66        // Convert messages to Responses API input format
     67        $input = array();
     68        foreach ( $messages as $m ) {
     69            $input[] = array(
     70                'role'    => $m['role'],
     71                'content' => array(
     72                    array( 'type' => 'input_text', 'text' => $m['content'] ),
     73                ),
     74            );
     75        }
     76
     77        $response = wp_remote_post( 'https://api.openai.com/v1/responses', array(
     78            'headers' => array(
     79                'Authorization' => 'Bearer ' . $api_key,
     80                'Content-Type' => 'application/json',
     81            ),
     82            'body' => wp_json_encode( array(
     83                'model' => $model ?: 'gpt-5',
     84                'tools' => array( array( 'type' => 'web_search' ) ),
     85                'input' => $input,
     86                'text' => array(
     87                    'format' => array(
     88                        'type' => 'json_schema',
     89                        'name' => 'json_responder',
     90                        'schema' => $schema,
     91                        'strict' => true,
     92                    )
     93                ),
     94            ) ),
     95            'timeout' => 180,
     96        ) );
     97
     98        if ( is_wp_error( $response ) ) {
     99            return $response;
     100        }
     101
     102        $body = wp_remote_retrieve_body( $response );
     103        $data = json_decode( $body, true );
     104
     105        // Responses API returns output_text or output tree
     106        if ( isset( $data['output_text'] ) && is_string( $data['output_text'] ) && $data['output_text'] !== '' ) {
     107            return $data['output_text'];
     108        }
     109        if ( isset( $data['output'][0]['content'][0]['text'] ) && is_string( $data['output'][0]['content'][0]['text'] ) ) {
     110            return $data['output'][0]['content'][0]['text'];
     111        }
     112
     113        if ( isset( $data['error']['message'] ) ) {
     114            return new WP_Error( 'api_error', 'OpenAI API Error: ' . sanitize_text_field( $data['error']['message'] ) );
     115        }
     116
     117        return new WP_Error( 'invalid_response', 'Invalid response from OpenAI Responses API' );
    106118       
    107119    } elseif ( $provider === 'anthropic' ) {
     
    141153        }
    142154
    143         // Prepare request body for Anthropic
     155        // Prepare request body for Anthropic (enable web search + JSON tool)
    144156        $request_body = array(
    145157            'model'       => $model ?: 'claude-sonnet-4-20250514',
    146158            'messages'    => $anthropic_messages,
    147             'tool_choice' => array( 'type' => 'tool', 'name' => 'json_responder' ),
    148             'tools'       => array( $tool_schema ),
     159            'tool_choice' => 'auto',
     160            'tools'       => array( array( 'type' => 'web_search' ), $tool_schema ),
    149161        );
    150162
     
    188200        if ( isset( $data['content'] ) && is_array( $data['content'] ) ) {
    189201            foreach ( $data['content'] as $content_block ) {
    190                 if ( isset( $content_block['type'] ) && 'tool_use' === $content_block['type'] && isset( $content_block['input'] ) ) {
     202                if (
     203                    isset( $content_block['type'] ) &&
     204                    'tool_use' === $content_block['type'] &&
     205                    isset( $content_block['name'] ) &&
     206                    'json_responder' === $content_block['name'] &&
     207                    isset( $content_block['input'] )
     208                ) {
    191209                    return wp_json_encode( $content_block['input'] );
    192210                }
     
    548566        $content = $data['candidates'][0]['content']['parts'][0]['text'];
    549567
    550     } elseif ( $provider === 'openai' ) {
    551         // Request to OpenAI API
    552         $api_key = get_option( 'saiap_openai_api_key' );
    553 
    554         if ( empty( $api_key ) ) {
    555             throw new Exception( 'OpenAI API key is required. Please enter your API key in the settings.' );
    556         }
    557 
    558         $response = wp_remote_post(
    559             'https://api.openai.com/v1/chat/completions',
    560             array(
    561                 'headers' => array(
    562                     'Authorization' => 'Bearer ' . $api_key,
    563                     'Content-Type'  => 'application/json',
    564                 ),
    565                 'body'    => wp_json_encode( $request_data ),
    566                 'timeout' => 120,
    567             )
    568         );
    569 
    570         if ( is_wp_error( $response ) ) {
    571             throw new Exception( esc_html( $response->get_error_message() ) );
    572         }
    573 
    574         $status_code = wp_remote_retrieve_response_code( $response );
    575         if ( $status_code !== 200 ) {
    576             $error_message = 'OpenAI API error: ';
    577             $body          = wp_remote_retrieve_body( $response );
    578             $data          = json_decode( $body, true );
    579 
    580             if ( isset( $data['error']['message'] ) ) {
    581                 $error_message .= sanitize_text_field( $data['error']['message'] );
    582             } else {
    583                 $error_message .= 'HTTP status ' . $status_code;
    584             }
    585 
    586             throw new Exception( esc_html( $error_message ) );
    587         }
    588 
    589         $body = wp_remote_retrieve_body( $response );
    590         $data = json_decode( $body, true );
    591 
    592         if ( ! $data || ! isset( $data['choices'][0]['message']['content'] ) ) {
    593             throw new Exception( 'Invalid response from OpenAI API. Please check your API key and try again.' );
    594         }
    595 
    596         $content = $data['choices'][0]['message']['content'];
     568    } elseif ( $provider === 'openai' ) {
     569        // Request to OpenAI Responses API
     570        $api_key = get_option( 'saiap_openai_api_key' );
     571
     572        if ( empty( $api_key ) ) {
     573            throw new Exception( 'OpenAI API key is required. Please enter your API key in the settings.' );
     574        }
     575
     576        // Convert messages to Responses API input format
     577        $input = array();
     578        foreach ( $request_data['messages'] as $m ) {
     579            $input[] = array(
     580                'role'    => $m['role'],
     581                'content' => array(
     582                    array( 'type' => 'input_text', 'text' => $m['content'] ),
     583                ),
     584            );
     585        }
     586
     587        $response = wp_remote_post(
     588            'https://api.openai.com/v1/responses',
     589            array(
     590                'headers' => array(
     591                    'Authorization' => 'Bearer ' . $api_key,
     592                    'Content-Type'  => 'application/json',
     593                ),
     594                'body'    => wp_json_encode( array(
     595                    'model' => $request_data['model'],
     596                    'tools' => array( array( 'type' => 'web_search' ) ),
     597                    'input' => $input,
     598                ) ),
     599                'timeout' => 180,
     600            )
     601        );
     602
     603        if ( is_wp_error( $response ) ) {
     604            throw new Exception( esc_html( $response->get_error_message() ) );
     605        }
     606
     607        $status_code = wp_remote_retrieve_response_code( $response );
     608        if ( $status_code !== 200 ) {
     609            $error_message = 'OpenAI API error: ';
     610            $body          = wp_remote_retrieve_body( $response );
     611            $data          = json_decode( $body, true );
     612
     613            if ( isset( $data['error']['message'] ) ) {
     614                $error_message .= sanitize_text_field( $data['error']['message'] );
     615            } else {
     616                $error_message .= 'HTTP status ' . $status_code;
     617            }
     618
     619            throw new Exception( esc_html( $error_message ) );
     620        }
     621
     622        $body = wp_remote_retrieve_body( $response );
     623        $data = json_decode( $body, true );
     624
     625        // Responses API returns output_text or output tree
     626        if ( isset( $data['output_text'] ) && is_string( $data['output_text'] ) && $data['output_text'] !== '' ) {
     627            $content = $data['output_text'];
     628        } elseif ( isset( $data['output'][0]['content'][0]['text'] ) && is_string( $data['output'][0]['content'][0]['text'] ) ) {
     629            $content = $data['output'][0]['content'][0]['text'];
     630        } else {
     631            throw new Exception( 'Invalid response from OpenAI Responses API. Please check your API key and try again.' );
     632        }
    597633    } else {
    598634        // Unknown provider
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/ai-settings.php

    r3373464 r3387025  
    9191
    9292/**
     93 * AJAX callback for saving notification settings
     94 */
     95function saiap_save_notifications_callback() {
     96    // Check nonce for security
     97    if ( ! isset( $_POST['saiap_notifications_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['saiap_notifications_nonce'] ) ), 'saiap_save_notifications_action' ) ) {
     98        wp_send_json_error( __( 'Security check failed', 'clearpost-simple-ai-auto-post' ) );
     99    }
     100
     101    if ( ! current_user_can( 'manage_options' ) ) {
     102        wp_send_json_error( __( 'Unauthorized', 'clearpost-simple-ai-auto-post' ) );
     103    }
     104
     105    $enabled    = isset( $_POST['saiap_notify_on_schedule'] ) ? sanitize_text_field( wp_unslash( $_POST['saiap_notify_on_schedule'] ) ) : 'no';
     106    $recipients = isset( $_POST['saiap_notify_recipients'] ) ? sanitize_text_field( wp_unslash( $_POST['saiap_notify_recipients'] ) ) : '';
     107
     108    update_option( 'saiap_notify_on_schedule', $enabled === 'yes' ? 'yes' : 'no' );
     109    update_option( 'saiap_notify_recipients', $recipients );
     110
     111    wp_send_json_success( array( 'message' => __( 'Notification settings saved!', 'clearpost-simple-ai-auto-post' ) ) );
     112}
     113
     114add_action( 'wp_ajax_saiap_save_notifications', 'saiap_save_notifications_callback' );
     115
     116/**
    93117 * Render the Settings tab
    94118 */
    95119function saiap_render_ai_settings_tab( $models = array() ) {
     120    $license_key = get_option( 'saiap_license_key', '' );
    96121    ?>
    97122    <div class="tab-content ai-settings-tab-content">
    98123        <div class="container">
     124            <?php if ( empty( $license_key ) ) : ?>
    99125            <div class="col col-12">
    100126                <div class="card">
    101127                    <h2 class="spacing-16-row"><?php esc_html_e( 'API Settings', 'clearpost-simple-ai-auto-post' ); ?></h2>
    102                     <p class="spacing-16-row"><?php esc_html_e( 'Enter your API keys for any providers you wish to use.', 'clearpost-simple-ai-auto-post' ); ?></p>
    103                     <p class="spacing-16-row"><?php esc_html_e( 'Note: You don\'t have to do this if you\'re using our premium cloud service.', 'clearpost-simple-ai-auto-post' ); ?></p>
     128                    <p class="spacing-16-row"><?php esc_html_e( 'Enter your API keys for any LLM providers you wish to use.', 'clearpost-simple-ai-auto-post' ); ?></p>
     129                    <p class="spacing-16-row"><?php esc_html_e( 'Note: You don\'t have to do this if you\'re using ClearPost Premium (no API keys needed).', 'clearpost-simple-ai-auto-post' ); ?></p>
    104130                    <div class="spacing-8-row">
    105                         <a href="https://clearpostplugin.com/#pricing" target="_blank" class="secondary-button"><?php esc_html_e( 'Try Premium (no API keys needed)', 'clearpost-simple-ai-auto-post' ); ?></a>
     131                        <a href="https://clearpostplugin.com/" target="_blank" class="secondary-button"><?php esc_html_e( 'Grow organic traffic on autopilot (no API keys needed)', 'clearpost-simple-ai-auto-post' ); ?></a>
    106132                    </div>
    107133                   
     
    145171                </div>
    146172            </div>
     173            <?php endif; ?>
     174
     175            <div class="col col-12">
     176                <div class="card">
     177                    <h2 class="spacing-16-row"><?php esc_html_e( 'Notifications', 'clearpost-simple-ai-auto-post' ); ?></h2>
     178                    <p class="spacing-16-row"><?php esc_html_e( 'Get an email when the scheduler creates a new draft.', 'clearpost-simple-ai-auto-post' ); ?></p>
     179
     180                    <form id="saiap-notifications-form" method="post">
     181                        <?php wp_nonce_field( 'saiap_save_notifications_action', 'saiap_notifications_nonce' ); ?>
     182
     183                        <div class="spacing-16-row">
     184                            <label for="saiap_notify_on_schedule" class="body-text"><?php esc_html_e( 'Enable email notifications', 'clearpost-simple-ai-auto-post' ); ?></label>
     185                            <select id="saiap_notify_on_schedule" name="saiap_notify_on_schedule" class="regular-text">
     186                                <option value="no" <?php selected( get_option( 'saiap_notify_on_schedule', 'no' ), 'no' ); ?>><?php esc_html_e( 'No', 'clearpost-simple-ai-auto-post' ); ?></option>
     187                                <option value="yes" <?php selected( get_option( 'saiap_notify_on_schedule', 'no' ), 'yes' ); ?>><?php esc_html_e( 'Yes', 'clearpost-simple-ai-auto-post' ); ?></option>
     188                            </select>
     189                        </div>
     190
     191                        <div class="spacing-16-row">
     192                            <label for="saiap_notify_recipients" class="body-text"><?php esc_html_e( 'Recipients (comma-separated emails)', 'clearpost-simple-ai-auto-post' ); ?></label>
     193                            <input type="text" id="saiap_notify_recipients" name="saiap_notify_recipients" class="large-text" value="<?php echo esc_attr( get_option( 'saiap_notify_recipients', get_option( 'admin_email', '' ) ) ); ?>" />
     194                            <p class="small-text"><?php esc_html_e( 'Defaults to the site admin email.', 'clearpost-simple-ai-auto-post' ); ?></p>
     195                        </div>
     196
     197                        <div class="spacing-24 text-align-center">
     198                            <button type="button" id="saiap-save-notifications" class="primary-button" onclick="saveNotificationSettings()">
     199                                <?php esc_html_e( 'Save Notification Settings', 'clearpost-simple-ai-auto-post' ); ?>
     200                            </button>
     201                        </div>
     202
     203                        <div id="notifications-settings-message" class="spacing-16" style="display: none;"></div>
     204                    </form>
     205                </div>
     206            </div>
    147207        </div>
    148208    </div>
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/editor-chat.php

    r3373464 r3387025  
    6464    }
    6565
    66     // Get API key for the provider
    67     $api_key_option = 'saiap_' . $provider . '_api_key';
    68     $api_key = get_option( $api_key_option );
    69 
    70     if ( empty( $api_key ) ) {
    71         return new WP_Error( 'missing_api_key', 'API key not configured for ' . $provider );
    72     }
    73 
    74     // Create editing-specific system prompt
     66    // Premium-first: If license key exists, try premium chat endpoint before local provider
     67    $license_key = get_option( 'saiap_license_key', '' );
     68
     69    // Create editing-specific system prompt
    7570    $editing_system_prompt = "You are a friendly and helpful AI assistant and content editor for WordPress. Your goal is to help the user write and improve their blog post. You can perform edits, answer questions about the content, or just chat about the post.
    7671
     
    120115" . $current_content;
    121116
    122     // Prepare the user message
     117    // Prepare the user message
    123118    $user_message = "My request is: " . $message;
    124119
    125     // Make AI request based on provider
    126     try {
    127         $result = saiap_make_ai_request( $user_message, $provider, $model, $editing_system_prompt );
    128 
    129         if ( is_wp_error( $result ) ) {
    130             return $result;
    131         }
    132 
    133         // Parse the JSON response
    134         $parsed_response = json_decode( $result, true );
    135 
    136         // Check if the response is valid and contains the required fields
    137         if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $parsed_response ) || ! isset( $parsed_response['chat_message'] ) || ! isset( $parsed_response['edited_content'] ) ) {
    138             // If not, treat the entire response as a chat message and assume no edit was made.
    139             // This prevents overwriting content with an error or non-content response.
    140             return array(
    141                 'chat_message'   => $result, // The raw response from the AI.
    142                 'has_edit'       => false,
    143                 'edited_content' => '', // No changes to the content.
    144             );
    145         }
    146 
    147         // Sanitize the response
    148         $response = array(
    149             'chat_message' => sanitize_text_field( $parsed_response['chat_message'] ),
    150             'has_edit'     => isset( $parsed_response['has_edit'] ) ? (bool) $parsed_response['has_edit'] : true,
    151             'edited_content' => wp_kses_post( $parsed_response['edited_content'] ),
    152         );
    153 
    154         return $response;
    155 
    156     } catch ( Exception $e ) {
    157         return new WP_Error( 'api_request_failed', 'Failed to communicate with AI service: ' . $e->getMessage() );
    158     }
     120    // Attempt premium call if license is present
     121    if ( ! empty( $license_key ) ) {
     122        $domain   = function_exists( 'saiap_get_server_host' ) ? saiap_get_server_host() : wp_parse_url( home_url(), PHP_URL_HOST );
     123        $messages = array(
     124            array( 'role' => 'system', 'content' => $editing_system_prompt ),
     125            array( 'role' => 'user', 'content' => $user_message ),
     126        );
     127
     128        $premium_response = wp_remote_post(
     129            'https://saiap.gopurposego.com/api/chat/edit',
     130            array(
     131                'headers' => array(
     132                    'Content-Type' => 'application/json',
     133                ),
     134                'body'    => wp_json_encode(
     135                    array(
     136                        'license_key' => $license_key,
     137                        'domain'      => $domain,
     138                        'provider'    => $provider,
     139                        'model'       => $model,
     140                        'messages'    => $messages,
     141                    )
     142                ),
     143                'timeout' => 120,
     144            )
     145        );
     146
     147        if ( is_wp_error( $premium_response ) ) {
     148            // Network or request error — surface as request failure (no fallback)
     149            return new WP_Error( 'api_request_failed', esc_html( $premium_response->get_error_message() ) );
     150        }
     151
     152        $status_code = wp_remote_retrieve_response_code( $premium_response );
     153        $body        = wp_remote_retrieve_body( $premium_response );
     154
     155        if ( $status_code === 401 || $status_code === 403 ) {
     156            // Auth error — fall back to local provider if possible
     157        } elseif ( $status_code !== 200 ) {
     158            // Other server/provider error — surface without fallback
     159            $data = json_decode( $body, true );
     160            $msg  = isset( $data['error'] ) ? sanitize_text_field( $data['error'] ) : 'Premium service error';
     161            return new WP_Error( 'api_error', $msg );
     162        } else {
     163            // 200 OK — parse structured response
     164            $data = json_decode( $body, true );
     165            if ( isset( $data['chat_message'] ) && isset( $data['edited_content'] ) && isset( $data['has_edit'] ) ) {
     166                return array(
     167                    'chat_message'   => sanitize_text_field( $data['chat_message'] ),
     168                    'has_edit'       => (bool) $data['has_edit'],
     169                    'edited_content' => wp_kses_post( $data['edited_content'] ),
     170                );
     171            }
     172
     173            // Invalid structured response
     174            return new WP_Error( 'invalid_response', 'Invalid response from premium chat API' );
     175        }
     176    }
     177
     178    // Fallback to local provider flow (or primary when no license)
     179    // Get API key for the provider
     180    $api_key_option = 'saiap_' . $provider . '_api_key';
     181    $api_key = get_option( $api_key_option );
     182
     183    if ( empty( $api_key ) ) {
     184        return new WP_Error( 'missing_api_key', 'API key not configured for ' . $provider );
     185    }
     186
     187    // Make AI request based on provider
     188    try {
     189        $result = saiap_make_ai_request( $user_message, $provider, $model, $editing_system_prompt );
     190
     191        if ( is_wp_error( $result ) ) {
     192            return $result;
     193        }
     194
     195        // Parse the JSON response
     196        $parsed_response = json_decode( $result, true );
     197
     198        // Check if the response is valid and contains the required fields
     199        if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $parsed_response ) || ! isset( $parsed_response['chat_message'] ) || ! isset( $parsed_response['edited_content'] ) ) {
     200            // If not, treat the entire response as a chat message and assume no edit was made.
     201            // This prevents overwriting content with an error or non-content response.
     202            return array(
     203                'chat_message'   => $result, // The raw response from the AI.
     204                'has_edit'       => false,
     205                'edited_content' => '', // No changes to the content.
     206            );
     207        }
     208
     209        // Sanitize the response
     210        $response = array(
     211            'chat_message' => sanitize_text_field( $parsed_response['chat_message'] ),
     212            'has_edit'     => isset( $parsed_response['has_edit'] ) ? (bool) $parsed_response['has_edit'] : true,
     213            'edited_content' => wp_kses_post( $parsed_response['edited_content'] ),
     214        );
     215
     216        return $response;
     217
     218    } catch ( Exception $e ) {
     219        return new WP_Error( 'api_request_failed', 'Failed to communicate with AI service: ' . $e->getMessage() );
     220    }
    159221}
    160222
     
    176238
    177239    $post_id = 0;
     240    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only access to current editor screen context
    178241    if ( isset( $_GET['post'] ) ) {
    179242        $post_id = intval( $_GET['post'] );
     
    187250            $post_type = $post->post_type;
    188251        }
     252    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only post_type detection in editor context
    189253    } elseif ( isset( $_GET['post_type'] ) ) {
    190254        $post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/generate.php

    r3373464 r3387025  
    144144            }
    145145        }
    146     } catch ( Exception $e ) {
    147         if ( function_exists( 'error_log' ) ) {
    148             error_log( '[SAIAP] Auto-image failed: ' . $e->getMessage() );
    149         }
    150         do_action( 'saiap_auto_image_failed', $post_id, new WP_Error( 'auto_image_exception', $e->getMessage() ) );
    151     }
     146    } catch ( Exception $e ) {
     147        do_action( 'saiap_auto_image_failed', $post_id, new WP_Error( 'auto_image_exception', $e->getMessage() ) );
     148    }
    152149
    153150    // Return success data
     
    450447                        <div class="notice notice-info inline">
    451448                            <p class="small-text" style="margin: 8px 0;">
    452                                 <?php esc_html_e( 'Skip provider setup and generate instantly with ClearPost Premium (no API keys needed).', 'clearpost-simple-ai-auto-post' ); ?>
    453                                 <a href="https://clearpostplugin.com/#pricing" target="_blank"><?php esc_html_e( 'Learn more', 'clearpost-simple-ai-auto-post' ); ?></a>
     449                                <?php esc_html_e( 'Grow organic traffic on autopilot while you sleep - with ClearPost Premium.', 'clearpost-simple-ai-auto-post' ); ?>
     450                                <a href="https://clearpostplugin.com/" target="_blank"><?php esc_html_e( 'Generate traffic on autopilot', 'clearpost-simple-ai-auto-post' ); ?></a>
    454451                            </p>
    455452                            <p style="margin: 8px 0;">
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/images.php

    r3373464 r3387025  
    169169
    170170    $file_array = array(
    171         'name'     => basename( parse_url( $imageUrl, PHP_URL_PATH ) ) ?: 'image.jpg',
     171        'name'     => basename( wp_parse_url( $imageUrl, PHP_URL_PATH ) ) ?: 'image.jpg',
    172172        'tmp_name' => $tmp,
    173173    );
    174174
    175175    $overrides = array( 'test_form' => false );
    176     $results   = wp_handle_sideload( $file_array, $overrides );
    177     if ( isset( $results['error'] ) ) {
    178         @unlink( $tmp );
    179         return new WP_Error( 'sideload_failed', $results['error'] );
    180     }
     176    $results   = wp_handle_sideload( $file_array, $overrides );
     177    if ( isset( $results['error'] ) ) {
     178        wp_delete_file( $tmp );
     179        return new WP_Error( 'sideload_failed', $results['error'] );
     180    }
    181181
    182182    $filename    = $results['file'];
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/licensing.php

    r3357104 r3387025  
    130130
    131131    ?>
     132    <?php if ( empty( $license_key ) ) : ?>
    132133    <div class="container">
    133134        <div class="card">
    134             <h3 class="spacing-16-row"><?php esc_html_e( 'AI that knows your voice and goals', 'clearpost-simple-ai-auto-post' ); ?></h3>
     135            <h3 class="spacing-16-row"><?php esc_html_e( 'Grow organic traffic on autopilot', 'clearpost-simple-ai-auto-post' ); ?></h3>
    135136           
    136137            <div class="row">
     
    138139                    <p class="body-text spacing-16-row">
    139140                    <?php
    140                         esc_html_e( 'Go further, faster with our Site Context Agent:', 'clearpost-simple-ai-auto-post' );
     141                        esc_html_e( 'Finally get found on Google. Get recommended by ChatGPT. Get the content engine that runs while you sleep.', 'clearpost-simple-ai-auto-post' );
    141142                    ?>
    142143                    </p>
    143144                    <ul>
    144                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Site context agent: Indexes and vectorizes your existing posts to understand your unique voice, formatting patterns, style, and topics.', 'clearpost-simple-ai-auto-post' ); ?></li>
    145                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Autonomous post scheduling: Get prompts and posts generating automatically while you sleep.', 'clearpost-simple-ai-auto-post' ); ?></li>
    146                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Enhanced AI chat: Your post editor copilot understands your full site context.', 'clearpost-simple-ai-auto-post' ); ?></li>
    147                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Your style, reproduced effortlessly: The agent creates custom AI instructions based on your site\'s existing content, so new posts automatically match your established style. Replaces hours of frustrating prompt engineering.', 'clearpost-simple-ai-auto-post' ); ?></li>
    148                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Maintain control: You can edit the AI-created system prompts for fine-grained control over how new posts come out.', 'clearpost-simple-ai-auto-post' ); ?></li>
    149                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Predictable price for AI usage: For a simple, predictable monthly subscription, with no nasty surprises from AI provider invoices.', 'clearpost-simple-ai-auto-post' ); ?></li>
     145                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Relax while your AI content engine pumps out content that drives organic traffic to your site.', 'clearpost-simple-ai-auto-post' ); ?></li>
     146                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Context + SEO AI Agent deeply analyzes your site, target audience, and objectives to produce a context architecture that ensures your new posts naturally rank and sound like you.', 'clearpost-simple-ai-auto-post' ); ?></li>
     147                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ ClearPost autonomously schedules and generates articles every day, then pings you to review and publish.', 'clearpost-simple-ai-auto-post' ); ?></li>
     148                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ AI Chat-to-Edit: Just chat with the AI and watch it edit your posts in real time.', 'clearpost-simple-ai-auto-post' ); ?></li>                     
     149                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Auto Google Image Search: Our AI does a Google Image search for you, and suggests images for every post.', 'clearpost-simple-ai-auto-post' ); ?></li>
     150                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ AI that lives in your site: ClearPost is WordPress-native, and always learning and getting smarter - so your content gets better and better over time.', 'clearpost-simple-ai-auto-post' ); ?></li>
    150151                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Premium support', 'clearpost-simple-ai-auto-post' ); ?></li>
    151152                    </ul>
     
    161162                       
    162163                        <p class="small-text" style="margin: 16px 0 8px 0;">
    163                             <?php esc_html_e( 'Imagine a simple way to use AI to accelerate your workflow. Drop a prompt, click Generate, and watch as Gutenberg blocks fill up with great content that fits your style and voice. Set up a week\'s worth of drafts in a coffee break.', 'clearpost-simple-ai-auto-post' ); ?>
     164                            <?php esc_html_e( 'Imagine the impact on your business when you start publishing great content every single day.', 'clearpost-simple-ai-auto-post' ); ?>
    164165                        </p>
    165166                       
     
    177178                               
    178179                    <div class="spacing-24">
    179                         <a href="https://clearpostplugin.com/#pricing" target="_blank" class="primary-button" style="display: inline-block; text-decoration: none;">
    180                             <?php esc_html_e( 'Upgrade Now', 'clearpost-simple-ai-auto-post' ); ?>
     180                        <a href="https://clearpostplugin.com/" target="_blank" class="primary-button" style="display: inline-block; text-decoration: none;">
     181                            <?php esc_html_e( 'Grow organic traffic on autopilot', 'clearpost-simple-ai-auto-post' ); ?>
    181182                        </a>
    182183                    </div>
     
    186187        </div>
    187188    </div>
     189    <?php endif; ?>
    188190    <div class="container">
    189191        <div class="card">
     
    234236                        <p class="small-text" style="margin: 0;">
    235237                            <?php esc_html_e( 'To cancel your subscription, please email ', 'clearpost-simple-ai-auto-post' ); ?>
    236                             <a href="mailto:support@gopurposego.com">support@gopurposego.com</a>
     238                            <a href="mailto:support@clearpostplugin.com">support@clearpostplugin.com</a>
    237239                        </p>
    238240                    </div>
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/onboarding.php

    r3373464 r3387025  
    1919    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    2020    if ( is_admin() && isset( $_GET['page'] ) && sanitize_text_field( wp_unslash( $_GET['page'] ) ) === 'saiap' ) {
    21         add_action( 'admin_notices', 'saiap_render_onboarding_checklist' );
     21        // Do not show onboarding for premium users (license key present)
     22        $license_key = get_option( 'saiap_license_key', '' );
     23        if ( empty( $license_key ) ) {
     24            add_action( 'admin_notices', 'saiap_render_onboarding_checklist' );
     25        }
    2226    }
    2327
     
    102106 */
    103107function saiap_render_onboarding_checklist() {
     108    // Do not render onboarding for premium users (license key present)
     109    $license_key = get_option( 'saiap_license_key', '' );
     110    if ( ! empty( $license_key ) ) {
     111        return;
     112    }
     113
    104114    // Don't show if dismissed or complete
    105115    if ( saiap_onboarding_is_dismissed() || saiap_onboarding_is_complete() ) {
     
    130140                <h3><?php esc_html_e( 'Get Started with ClearPost Simple AI Auto Post', 'clearpost-simple-ai-auto-post' ); ?></h3>
    131141                <p><?php esc_html_e( 'Complete these steps to start generating content with AI:', 'clearpost-simple-ai-auto-post' ); ?></p>
    132             </div>
    133            
    134             <div class="saiap-onboarding-progress">
    135                 <div class="saiap-progress-bar">
    136                     <div class="saiap-progress-fill" style="width: <?php echo esc_attr( $progress_percentage ); ?>%"></div>
    137                 </div>
    138                 <span class="saiap-progress-text"><?php echo esc_html( $completed_steps ); ?>/3 <?php esc_html_e( 'steps completed', 'clearpost-simple-ai-auto-post' ); ?></span>
    139142            </div>
    140143           
     
    152155                        <p><?php esc_html_e( 'Configure your OpenAI, Anthropic, or Google Gemini API key to enable AI content generation.', 'clearpost-simple-ai-auto-post' ); ?></p>
    153156                        <?php if ( ! $step1_complete ) : ?>
    154                             <a href="?page=saiap&tab=ai-settings" class="saiap-step-link"><?php esc_html_e( 'Add API Key →', 'clearpost-simple-ai-auto-post' ); ?></a>
    155                             <div class="spacing-8">
    156                                 <a href="https://clearpostplugin.com/#pricing" target="_blank" rel="noopener" class="saiap-step-link">
    157                                     <?php esc_html_e( 'Or try Premium (no API keys needed)', 'clearpost-simple-ai-auto-post' ); ?>
    158                                 </a>
    159                             </div>
     157                        <a href="?page=saiap&tab=ai-settings" class="button button-primary saiap-step-primary-action"><?php esc_html_e( 'Add API Key', 'clearpost-simple-ai-auto-post' ); ?></a>
    160158                        <?php endif; ?>
    161159                    </div>
     
    198196           
    199197            <div class="saiap-onboarding-help">
    200                 <!-- TODO: Update this link to the new getting started guide -->
    201                 <p><?php esc_html_e( 'Need help?', 'clearpost-simple-ai-auto-post' ); ?> <a href="#" class="saiap-help-link" target="_blank"><?php esc_html_e( 'View our getting started guide', 'clearpost-simple-ai-auto-post' ); ?></a></p>
     198                <p><?php esc_html_e( 'Need help?', 'clearpost-simple-ai-auto-post' ); ?> <a href="https://clearpostplugin.com/getting-started-with-clearpost-wordpress-plugin/" class="saiap-help-link" target="_blank"><?php esc_html_e( 'View our getting started guide', 'clearpost-simple-ai-auto-post' ); ?></a></p>
     199                <p class="saiap-onboarding-upsell-footnote"><a href="https://clearpostplugin.com/" target="_blank" rel="noopener noreferrer" class="saiap-help-link button button-primary button-hero"><?php esc_html_e( 'Skip the setup and start generating traffic on autopilot', 'clearpost-simple-ai-auto-post' ); ?></a></p>
    202200            </div>
    203201        </div>
     
    239237    // Only load on plugin settings page
    240238    if ( $hook !== 'toplevel_page_saiap' ) {
     239        return;
     240    }
     241
     242    // Do not load onboarding assets for premium users (license key present)
     243    $license_key = get_option( 'saiap_license_key', '' );
     244    if ( ! empty( $license_key ) ) {
    241245        return;
    242246    }
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/scheduler.php

    r3373464 r3387025  
    510510            if ( is_wp_error( $result ) ) {
    511511                $status_to_set = 'failed';
     512                /**
     513                 * Fires when scheduled post generation fails.
     514                 *
     515                 * @since 1.1.9
     516                 * @param array $prompt Prompt payload
     517                 * @param WP_Error $error Error object
     518                 */
     519                do_action( 'saiap_scheduler_post_generation_failed', $prompt, $result );
     520            } else {
     521                // On success, optionally notify via email
     522                $notify = get_option( 'saiap_notify_on_schedule', 'no' );
     523                if ( 'yes' === $notify && ! empty( $result['post_id'] ) ) {
     524                    $post_id   = absint( $result['post_id'] );
     525                    $post      = get_post( $post_id );
     526                    $site_name = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
     527                    $edit_url  = get_edit_post_link( $post_id, '' );
     528                    if ( empty( $edit_url ) ) {
     529                        // Cron may lack a user context; fall back to admin edit URL which will prompt login if needed
     530                        $edit_url = admin_url( 'post.php?post=' . $post_id . '&action=edit' );
     531                    }
     532
     533                    // Build recipients from option (CSV), sanitize, dedupe
     534                    $raw_recipients = get_option( 'saiap_notify_recipients', get_option( 'admin_email', '' ) );
     535                    $emails = array_filter( array_map( 'trim', explode( ',', (string) $raw_recipients ) ) );
     536                    $emails = array_unique( array_values( array_filter( $emails, 'is_email' ) ) );
     537
     538                    if ( ! empty( $emails ) ) {
     539                        // Subject and body with i18n
     540                        $subject = sprintf(
     541                            /* translators: %s: post title */
     542                            __( 'ClearPost: New AI draft — %s', 'clearpost-simple-ai-auto-post' ),
     543                            $post ? $post->post_title : __( '(untitled)', 'clearpost-simple-ai-auto-post' )
     544                        );
     545
     546                        // Created at in site timezone
     547                        $tz      = wp_timezone();
     548                        $created = new DateTime( 'now', $tz );
     549                        $created_str = $created->format( 'Y-m-d H:i' );
     550
     551                        $body_lines = array(
     552                            /* translators: %s: site name */
     553                            sprintf( __( 'Site: %s', 'clearpost-simple-ai-auto-post' ), $site_name ),
     554                            /* translators: %s: post title */
     555                            sprintf( __( 'Title: %s', 'clearpost-simple-ai-auto-post' ), $post ? $post->post_title : '' ),
     556                            /* translators: %s: post status */
     557                            sprintf( __( 'Status: %s', 'clearpost-simple-ai-auto-post' ), __( 'Draft', 'clearpost-simple-ai-auto-post' ) ),
     558                            /* translators: %s: created date/time */
     559                            sprintf( __( 'Created: %s', 'clearpost-simple-ai-auto-post' ), $created_str ),
     560                            /* translators: %s: edit URL */
     561                            sprintf( __( 'Edit link: %s', 'clearpost-simple-ai-auto-post' ), $edit_url ),
     562                            '',
     563                            __( 'This draft was generated by ClearPost. Review, edit, and publish when ready.', 'clearpost-simple-ai-auto-post' ),
     564                        );
     565
     566                        $email_payload = array(
     567                            'recipients' => $emails,
     568                            'subject'    => $subject,
     569                            'body'       => implode( "\n", $body_lines ),
     570                            'headers'    => array( 'Content-Type: text/plain; charset=UTF-8' ),
     571                        );
     572
     573                        /**
     574                         * Filter the scheduler notification email payload.
     575                         *
     576                         * @since 1.1.9
     577                         * @param array $email_payload { recipients, subject, body, headers }
     578                         * @param int $post_id Generated post ID
     579                         * @param array $prompt Prompt payload
     580                         */
     581                        $email_payload = apply_filters( 'saiap_scheduler_notification_email', $email_payload, $post_id, $prompt );
     582
     583                        if ( ! empty( $email_payload['recipients'] ) && ! empty( $email_payload['subject'] ) && isset( $email_payload['body'] ) ) {
     584                            // Send the email (one message, multiple recipients)
     585                            wp_mail( (array) $email_payload['recipients'], (string) $email_payload['subject'], (string) $email_payload['body'], (array) ( $email_payload['headers'] ?? array() ) );
     586                        }
     587                    }
     588                }
     589
     590                /**
     591                 * Fires when a scheduled post was generated successfully.
     592                 *
     593                 * @since 1.1.9
     594                 * @param array $prompt Prompt payload
     595                 * @param array $result Result from orchestrator (post_id, edit_url)
     596                 */
     597                do_action( 'saiap_scheduler_post_generated', $prompt, $result );
    512598            }
    513599
    514600        } catch ( Exception $e ) {
    515601            $status_to_set = 'failed';
     602            do_action( 'saiap_scheduler_post_generation_failed', $prompt, new WP_Error( 'scheduler_exception', $e->getMessage() ) );
    516603        }
    517604
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/site-context.php

    r3373464 r3387025  
    3838            <div class="col col-12">
    3939                <div class="card spacing-24">
    40                     <h2 class="spacing-16-row"><?php esc_html_e( 'Site Context Agent', 'clearpost-simple-ai-auto-post' ); ?></h2>
     40                    <h2 class="spacing-16-row"><?php esc_html_e( 'Context + SEO AI Agent', 'clearpost-simple-ai-auto-post' ); ?></h2>
    4141                   
    4242                    <div class="spacing-16">
    4343                        <p class="body-text">
    44                             <?php esc_html_e( 'The AI Site Context Agent uses our cloud service to analyze your posts, generates vector embeddings, and creates a context architecture to automatically create 10x more relevant content that matches your site\'s existing style and topics.', 'clearpost-simple-ai-auto-post' ); ?>
     44                            <?php esc_html_e( 'The Context + SEO AI Agent autonomously learns your site, runs SEO analysis, finds the best keywords in your niche, and maintains a context architecture to create 10x more relevant content that naturally ranks.', 'clearpost-simple-ai-auto-post' ); ?>
    4545                        </p>
    4646                       
     
    352352            $taxonomy_data = array();
    353353
    354             error_log( '[TAXONOMY DEBUG] Processing post ID ' . $post->ID . ' - Title: "' . $post->post_title . '"' );
    355             error_log( '[TAXONOMY DEBUG] Post type: ' . $post_type );
    356             error_log( '[TAXONOMY DEBUG] Available taxonomies: ' . print_r( array_keys( $all_taxonomies ), true ) );
     354           
    357355
    358356            foreach ( $all_taxonomies as $tax_slug => $taxonomy ) {
    359                 error_log( '[TAXONOMY DEBUG] Processing taxonomy: ' . $tax_slug . ' (hierarchical: ' . ( $taxonomy->hierarchical ? 'true' : 'false' ) . ')' );
     357               
    360358               
    361359                $terms = wp_get_post_terms( $post->ID, $tax_slug, array( 'fields' => 'all' ) );
    362360
    363                 error_log( '[TAXONOMY DEBUG] Raw terms for ' . $tax_slug . ': ' . print_r( $terms, true ) );
     361               
    364362
    365363                if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
     
    381379                    );
    382380                   
    383                     error_log( '[TAXONOMY DEBUG] Raw term data for ' . $tax_slug . ': ' . print_r( $raw_term_data, true ) );
     381                   
    384382                } else {
    385                     error_log( '[TAXONOMY DEBUG] No terms found for ' . $tax_slug . ' or error occurred' );
     383                   
    386384                }
    387385            }
    388386
    389             error_log( '[TAXONOMY DEBUG] Final taxonomy data for post ' . $post->ID . ': ' . print_r( $taxonomy_data, true ) );
     387           
    390388           
    391389            $formatted_posts[] = array(
     
    491489        );
    492490       
    493         error_log( '[TAXONOMY DEBUG] Sending ' . count( $posts_for_api ) . ' posts to API. Sample post data: ' . print_r( array_slice( $posts_for_api, 0, 2 ), true ) );
     491       
    494492
    495493        $response = wp_remote_post(
  • clearpost-simple-ai-auto-post/tags/1.2.0/includes/taxonomy.php

    r3373464 r3387025  
    2727 */
    2828function saiap_apply_post_taxonomy( $post_id, $taxonomy_suggestions = array() ) {
    29     error_log( '[TAXONOMY APPLY DEBUG] Starting taxonomy application for post ID: ' . $post_id );
    30     error_log( '[TAXONOMY APPLY DEBUG] Taxonomy suggestions received: ' . print_r( $taxonomy_suggestions, true ) );
    3129   
    3230    if ( ! $post_id || ! is_numeric( $post_id ) ) {
     
    4442    if ( ! empty( $taxonomy_suggestions ) && is_array( $taxonomy_suggestions ) ) {
    4543        foreach ( $taxonomy_suggestions as $tax_slug => $terms ) {
    46             error_log( '[TAXONOMY APPLY DEBUG] Processing taxonomy: ' . $tax_slug . ' with terms: ' . print_r( $terms, true ) );
    47            
    48             if ( empty( $terms ) ) {
    49                 error_log( '[TAXONOMY APPLY DEBUG] Skipping empty terms for ' . $tax_slug );
    50                 continue;
    51             }
     44           
     45           
     46            if ( empty( $terms ) ) {
     47                continue;
     48            }
    5249            $taxonomy_obj = get_taxonomy( $tax_slug );
    53             if ( ! $taxonomy_obj ) {
    54                 error_log( '[TAXONOMY APPLY DEBUG] Taxonomy object not found for ' . $tax_slug );
    55                 continue;
    56             }
    57            
    58             error_log( '[TAXONOMY APPLY DEBUG] Taxonomy ' . $tax_slug . ' is hierarchical: ' . ( $taxonomy_obj->hierarchical ? 'true' : 'false' ) );
     50            if ( ! $taxonomy_obj ) {
     51                continue;
     52            }
     53           
     54           
    5955           
    6056            $term_ids = array();
    6157           
    6258            // Handle raw terms from AI
    63             if ( is_array( $terms ) && isset( $terms[0] ) && is_array( $terms[0] ) && isset( $terms[0]['name'] ) ) {
    64                 error_log( '[TAXONOMY APPLY DEBUG] Processing raw term data format for ' . $tax_slug );
     59            if ( is_array( $terms ) && isset( $terms[0] ) && is_array( $terms[0] ) && isset( $terms[0]['name'] ) ) {
    6560               
    6661                // First pass: Create all terms and build a map of suggested term_id to actual term_id
     
    7671                    }
    7772
    78                     error_log( '[TAXONOMY APPLY DEBUG] Processing term: ' . $term_name . ' (suggested parent: ' . $suggested_parent . ')' );
     73                   
    7974                   
    8075                    // Check if term exists by name
     
    8681                            // For hierarchical taxonomies, we need to find the actual parent term_id
    8782                            $actual_parent_id = isset( $term_id_map[ $suggested_parent ] ) ? $term_id_map[ $suggested_parent ] : 0;
    88                             if ( $actual_parent_id > 0 ) {
    89                                 $term_args['parent'] = $actual_parent_id;
    90                                 error_log( '[TAXONOMY APPLY DEBUG] Setting parent for ' . $term_name . ' to ' . $actual_parent_id );
    91                             }
     83                            if ( $actual_parent_id > 0 ) {
     84                                $term_args['parent'] = $actual_parent_id;
     85                            }
    9286                        }
    9387                       
    9488                        $term_result = wp_insert_term( $term_name, $tax_slug, $term_args );
    95                         if ( ! is_wp_error( $term_result ) ) {
     89                        if ( ! is_wp_error( $term_result ) ) {
    9690                            $actual_term_id = $term_result['term_id'];
    9791                            $term_ids[] = $actual_term_id;
     
    9993                            $suggested_term_id = isset( $term_data['term_id'] ) ? intval( $term_data['term_id'] ) : count( $term_id_map ) + 1;
    10094                            $term_id_map[ $suggested_term_id ] = $actual_term_id;
    101                             error_log( '[TAXONOMY APPLY DEBUG] Created new term: ' . $term_name . ' (ID: ' . $actual_term_id . ')' );
    10295                        } else {
    10396                            $errors[] = $term_result->get_error_message();
    104                             error_log( '[TAXONOMY APPLY DEBUG] Error creating term ' . $term_name . ': ' . $term_result->get_error_message() );
    10597                        }
    10698                    } else {
     
    108100                        $actual_term_id = $existing_term->term_id;
    109101                        $term_ids[] = $actual_term_id;
    110                         // Map the suggested term_id to the actual term_id
     102                        // Map the suggested term_id to the actual term_id
    111103                        $suggested_term_id = isset( $term_data['term_id'] ) ? intval( $term_data['term_id'] ) : count( $term_id_map ) + 1;
    112104                        $term_id_map[ $suggested_term_id ] = $actual_term_id;
    113                         error_log( '[TAXONOMY APPLY DEBUG] Found existing term: ' . $term_name . ' (ID: ' . $actual_term_id . ')' );
     105                       
    114106                    }
    115107                }
     
    127119                           
    128120                            if ( $actual_term_id > 0 && $actual_parent_id > 0 ) {
    129                                 $update_result = wp_update_term( $actual_term_id, $tax_slug, array( 'parent' => $actual_parent_id ) );
    130                                 if ( ! is_wp_error( $update_result ) ) {
    131                                     error_log( '[TAXONOMY APPLY DEBUG] Updated parent for term ' . $term_name . ' (ID: ' . $actual_term_id . ') to parent ID: ' . $actual_parent_id );
    132                                 } else {
    133                                     error_log( '[TAXONOMY APPLY DEBUG] Error updating parent for term ' . $term_name . ': ' . $update_result->get_error_message() );
    134                                 }
     121                                $update_result = wp_update_term( $actual_term_id, $tax_slug, array( 'parent' => $actual_parent_id ) );
     122                                if ( is_wp_error( $update_result ) ) {
     123                                    // collect error silently
     124                                }
    135125                            }
    136126                        }
     
    139129            }
    140130           
    141             if ( ! empty( $term_ids ) ) {
    142                 $all_term_ids[ $tax_slug ] = $term_ids;
    143                 error_log( '[TAXONOMY APPLY DEBUG] Final term IDs for ' . $tax_slug . ': ' . print_r( $term_ids, true ) );
    144             }
    145         }
    146     }
    147 
    148     error_log( '[TAXONOMY APPLY DEBUG] All term IDs to apply: ' . print_r( $all_term_ids, true ) );
     131            if ( ! empty( $term_ids ) ) {
     132                $all_term_ids[ $tax_slug ] = $term_ids;
     133            }
     134        }
     135    }
     136
     137   
    149138
    150139    $success_count = 0;
    151140    foreach ( $all_term_ids as $tax_slug => $term_ids_to_apply ) {
    152         error_log( '[TAXONOMY APPLY DEBUG] Applying ' . count( $term_ids_to_apply ) . ' terms to ' . $tax_slug );
    153141        $result = wp_set_post_terms( $post_id, $term_ids_to_apply, $tax_slug, true ); // Append terms
    154         if ( ! is_wp_error( $result ) ) {
     142        if ( ! is_wp_error( $result ) ) {
    155143            $success_count += count( $term_ids_to_apply );
    156             error_log( '[TAXONOMY APPLY DEBUG] Successfully applied terms to ' . $tax_slug );
    157144        } else {
    158145            $errors[] = $result->get_error_message();
    159             error_log( '[TAXONOMY APPLY DEBUG] Error applying terms to ' . $tax_slug . ': ' . $result->get_error_message() );
    160         }
    161     }
    162 
    163     error_log( '[TAXONOMY APPLY DEBUG] Application complete. Success count: ' . $success_count . ', Errors: ' . print_r( $errors, true ) );
     146        }
     147    }
     148
     149   
    164150
    165151    if ( ! empty( $errors ) ) {
     
    184170 */
    185171function saiap_create_nested_terms( $terms, $tax_slug, $parent_id, &$term_ids, &$errors ) {
    186     error_log( '[TAXONOMY NESTED DEBUG] Creating nested terms for taxonomy: ' . $tax_slug . ', parent_id: ' . $parent_id );
    187     error_log( '[TAXONOMY NESTED DEBUG] Terms to process: ' . print_r( $terms, true ) );
    188172   
    189173    foreach ( $terms as $key => $value ) {
     
    191175        $children = is_string( $key ) ? $value : array();
    192176
    193         error_log( '[TAXONOMY NESTED DEBUG] Processing term: ' . $term_name . ' (key: ' . $key . ')' );
    194         error_log( '[TAXONOMY NESTED DEBUG] Children for ' . $term_name . ': ' . print_r( $children, true ) );
     177       
    195178
    196179        if ( is_array( $term_name ) ) {
    197180            // Handle cases where the array is not associative
    198             error_log( '[TAXONOMY NESTED DEBUG] Term name is array, processing sub-items' );
     181           
    199182            foreach ( $term_name as $sub_key => $sub_value ) {
    200183                saiap_create_nested_terms( array( $sub_key => $sub_value ), $tax_slug, $parent_id, $term_ids, $errors );
     
    204187
    205188        $term_name = trim( $term_name );
    206         if ( empty( $term_name ) ) {
    207             error_log( '[TAXONOMY NESTED DEBUG] Skipping empty term name' );
    208             continue;
    209         }
    210 
    211         error_log( '[TAXONOMY NESTED DEBUG] Looking for existing term: ' . $term_name . ' in taxonomy: ' . $tax_slug );
     189        if ( empty( $term_name ) ) {
     190            continue;
     191        }
     192
    212193        $term = get_term_by( 'name', $term_name, $tax_slug );
    213194        if ( ! $term ) {
    214             error_log( '[TAXONOMY NESTED DEBUG] Term not found, creating new term: ' . $term_name . ' with parent_id: ' . $parent_id );
    215195            $term_result = wp_insert_term( $term_name, $tax_slug, array( 'parent' => $parent_id ) );
    216196            if ( ! is_wp_error( $term_result ) ) {
    217197                $current_term_id = $term_result['term_id'];
    218                 error_log( '[TAXONOMY NESTED DEBUG] Successfully created term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );
    219198            } else {
    220199                $errors[] = "Failed to create term '{$term_name}': " . $term_result->get_error_message();
    221                 error_log( '[TAXONOMY NESTED DEBUG] Error creating term ' . $term_name . ': ' . $term_result->get_error_message() );
    222200                continue;
    223201            }
    224202        } else {
    225203            $current_term_id = $term['term_id'];
    226             error_log( '[TAXONOMY NESTED DEBUG] Found existing term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );
    227204        }
    228205
    229206        $term_ids[] = $current_term_id;
    230207
    231         if ( ! empty( $children ) && is_array( $children ) ) {
    232             error_log( '[TAXONOMY NESTED DEBUG] Processing children for term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );
     208        if ( ! empty( $children ) && is_array( $children ) ) {
    233209            saiap_create_nested_terms( $children, $tax_slug, $current_term_id, $term_ids, $errors );
    234210        } else {
    235             error_log( '[TAXONOMY NESTED DEBUG] No children for term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );
    236         }
    237     }
    238    
    239     error_log( '[TAXONOMY NESTED DEBUG] Completed nested terms creation for taxonomy: ' . $tax_slug . ', parent_id: ' . $parent_id . ', term_ids: ' . print_r( $term_ids, true ) );
     211           
     212        }
     213    }
     214   
    240215}
    241216
     
    279254    }
    280255
    281     error_log( '[TAXONOMY API DEBUG] Sending taxonomy analysis request for post ID: ' . $post_id );
    282     error_log( '[TAXONOMY API DEBUG] Post content length: ' . strlen( $post_content ) );
    283     error_log( '[TAXONOMY API DEBUG] Available taxonomies: ' . print_r( $available_taxonomies, true ) );
     256   
    284257
    285258    // Attempt to get taxonomy_settings from cached context or fetch fresh
     
    290263    }
    291264    if ( is_array( $cached_context ) && isset( $cached_context['taxonomy_settings'] ) && ! empty( $cached_context['taxonomy_settings'] ) ) {
    292         $taxonomy_settings = $cached_context['taxonomy_settings'];
    293         error_log( '[TAXONOMY API DEBUG] Using cached taxonomy_settings' );
    294     }
    295     if ( empty( $taxonomy_settings ) ) {
    296         error_log( '[TAXONOMY API DEBUG] taxonomy_settings not in cache; fetching from /api/context' );
     265        $taxonomy_settings = $cached_context['taxonomy_settings'];
     266    }
     267    if ( empty( $taxonomy_settings ) ) {
    297268        $context_response = wp_remote_post(
    298269            'https://saiap.gopurposego.com/api/context',
     
    316287            $context_data   = json_decode( $context_body, true );
    317288            if ( 200 === $context_status && isset( $context_data['context']['taxonomy_settings'] ) && ! empty( $context_data['context']['taxonomy_settings'] ) ) {
    318                 $taxonomy_settings = $context_data['context']['taxonomy_settings'];
    319                 error_log( '[TAXONOMY API DEBUG] Retrieved taxonomy_settings from /api/context' );
     289                $taxonomy_settings = $context_data['context']['taxonomy_settings'];
    320290            }
    321291        }
     
    329299    if ( ! empty( $taxonomy_settings ) ) {
    330300        $payload['taxonomy_settings'] = is_array( $taxonomy_settings ) ? wp_json_encode( $taxonomy_settings ) : (string) $taxonomy_settings;
    331     } else {
    332         error_log( '[TAXONOMY API DEBUG] taxonomy_settings unavailable; proceeding with availableTaxonomies only' );
    333     }
     301    } else {
     302    }
    334303    $payload['availableTaxonomies'] = $available_taxonomies;
    335304
     
    345314    );
    346315
    347     error_log( '[TAXONOMY API DEBUG] API response received' );
     316   
    348317
    349318    if ( is_wp_error( $api_response ) ) {
     
    362331    $taxonomy_data = json_decode( $response_body, true );
    363332
    364     error_log( '[TAXONOMY API DEBUG] Raw API response: ' . $response_body );
    365     error_log( '[TAXONOMY API DEBUG] Parsed taxonomy data: ' . print_r( $taxonomy_data, true ) );
     333   
    366334
    367335    $result_key = null;
     
    371339        $result_key = 'taxonomy';
    372340    }
    373     if ( ! $result_key ) {
    374         error_log( '[TAXONOMY API DEBUG] No taxonomy suggestions received from AI' );
     341    if ( ! $result_key ) {
    375342        return new WP_Error( 'no_taxonomy_suggestions', 'No taxonomy suggestions received from AI' );
    376343    }
    377344
    378     error_log( '[TAXONOMY API DEBUG] Applying taxonomy suggestions: ' . print_r( $taxonomy_data[ $result_key ], true ) );
     345   
    379346
    380347    $apply_result = saiap_apply_post_taxonomy( $post_id, $taxonomy_data[ $result_key ] );
  • clearpost-simple-ai-auto-post/tags/1.2.0/readme.txt

    r3373470 r3387025  
    44Requires at least: 5.0
    55Tested up to: 6.8
    6 Stable tag: 1.1.8
     6Stable tag: 1.2.0
    77Requires PHP: 7.2
    88License: GPLv2 or later
  • clearpost-simple-ai-auto-post/tags/1.2.0/simple-ai-auto-post.php

    r3373470 r3387025  
    33Plugin Name: ClearPost Simple AI Auto Post | Create Content with AI
    44Description: Your AI Agent for SEO, in WordPress. An AI content marketer that knows your site, then schedules and generates posts every day.
    5 Plugin URI: https://gopurposego.com/simple-ai-blog-post-generator-wordpress-plugin/
    6 Version: 1.1.8
     5Plugin URI: https://clearpostplugin.com/
     6Version: 1.2.0
    77License: GPLv2 or later
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1818
    1919// Define plugin version
    20 define( 'SAIAP_VERSION', '1.1.8' );
     20define( 'SAIAP_VERSION', '1.2.0' );
    2121
    2222// Optionally enable Demo Mode (for internal testing only)
    2323$__saiap_demo_enabled = ( defined( 'SAIAP_DEMO_MODE' ) && SAIAP_DEMO_MODE );
    24 if ( function_exists( 'error_log' ) ) {
    25     error_log( '[SAIAP Demo] Constant present=' . ( defined( 'SAIAP_DEMO_MODE' ) ? 'yes' : 'no' ) . ', enabled=' . ( $__saiap_demo_enabled ? 'true' : 'false' ) );
    26 }
     24 
    2725
    2826if ( $__saiap_demo_enabled ) {
    2927    $__saiap_demo_bootstrap = plugin_dir_path( __FILE__ ) . 'dev/demo-mode/bootstrap.php';
    30     if ( ! file_exists( $__saiap_demo_bootstrap ) ) {
    31         if ( function_exists( 'error_log' ) ) {
    32             error_log( '[SAIAP Demo] Enabled but bootstrap not found at: ' . $__saiap_demo_bootstrap );
    33         }
     28    if ( ! file_exists( $__saiap_demo_bootstrap ) ) {
    3429        add_action( 'admin_notices', function() use ($__saiap_demo_bootstrap) {
    3530            echo '<div class="notice notice-warning"><p><strong>Simple AI Auto Post:</strong> Demo Mode is enabled but demo files were not found at <code>' . esc_html( $__saiap_demo_bootstrap ) . '</code>. Make sure you installed the plugin from the Git repo (release ZIPs exclude dev/).</p></div>';
    3631        } );
    37     } else {
    38         if ( function_exists( 'error_log' ) ) {
    39             error_log( '[SAIAP Demo] Loading bootstrap: ' . $__saiap_demo_bootstrap );
    40         }
     32    } else {
    4133        // Optional: debug console log to confirm load path (kept silent in UI)
    4234        add_action( 'admin_footer', function() use ($__saiap_demo_bootstrap) {
     
    193185    add_option( 'saiap_context_post_age', '365' ); // days
    194186
     187    // Notifications defaults
     188    $admin_email_default = get_option( 'admin_email', '' );
     189    add_option( 'saiap_notify_on_schedule', 'no' );
     190    add_option( 'saiap_notify_recipients', $admin_email_default );
     191
    195192    register_setting(
    196193        'saiap_options_group',
     
    256253            'sanitize_callback' => 'absint',
    257254            'default'           => 365,
     255        )
     256    );
     257
     258    // Register notification settings
     259    register_setting(
     260        'saiap_options_group',
     261        'saiap_notify_on_schedule',
     262        array(
     263            'type'              => 'string',
     264            'sanitize_callback' => 'sanitize_text_field',
     265            'default'           => 'no',
     266        )
     267    );
     268    register_setting(
     269        'saiap_options_group',
     270        'saiap_notify_recipients',
     271        array(
     272            'type'              => 'string',
     273            'sanitize_callback' => 'sanitize_text_field',
     274            'default'           => $admin_email_default,
    258275        )
    259276    );
  • clearpost-simple-ai-auto-post/trunk/assets/css/onboarding.css

    r3357104 r3387025  
    2929
    3030.saiap-onboarding-container {
    31     padding: 24px;
     31    padding: 18px; /* reduced ~25% */
    3232}
    3333
    3434/* Header section */
    3535.saiap-onboarding-header {
    36     margin-bottom: 20px;
     36    margin-bottom: 14px; /* reduced spacing */
    3737    text-align: center;
    3838}
     
    5858    align-items: center;
    5959    gap: 16px;
    60     margin-bottom: 24px;
    61     padding: 0 20px;
     60    margin-bottom: 16px; /* reduced spacing */
     61    padding: 0 14px; /* reduced side padding */
    6262}
    6363
     
    109109    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    110110    gap: 20px;
    111     margin-bottom: 20px;
     111    margin-bottom: 14px; /* reduced spacing */
    112112}
    113113
     
    117117    align-items: flex-start;
    118118    gap: 12px;
    119     padding: 16px;
     119    padding: 12px; /* reduced ~25% */
    120120    background: #ffffff;
    121121    border-radius: 8px;
     
    225225.saiap-onboarding-help {
    226226    text-align: center;
    227     padding-top: 16px;
     227    padding-top: 12px; /* reduced spacing */
    228228    border-top: 1px solid #e5e7eb;
    229229}
    230230
    231231.saiap-onboarding-help p {
    232     margin: 0;
    233     font-size: 14px;
    234     color: var(--text-secondary);
     232    margin: 4px 0; /* allow small separation for two lines */
     233    font-size: 14px;
     234    color: var(--text-secondary);
     235}
     236
     237/* Upsell footnote (muted) */
     238.saiap-onboarding-upsell-footnote {
     239    font-size: 12px;
     240    color: var(--text-secondary);
     241    opacity: 0.85;
    235242}
    236243
  • clearpost-simple-ai-auto-post/trunk/assets/js/admin.js

    r3373464 r3387025  
    283283    }
    284284
     285    // Notification Settings save function
     286    window.saveNotificationSettings = function() {
     287        var form = document.getElementById('saiap-notifications-form');
     288        var btn = document.getElementById('saiap-save-notifications');
     289
     290        if (btn) {
     291            btn.disabled = true;
     292            btn.textContent = 'Saving...';
     293        }
     294
     295        var formData = new FormData(form);
     296        formData.append('action', 'saiap_save_notifications');
     297
     298        var nonceField = document.querySelector('input[name="saiap_notifications_nonce"]');
     299        if (nonceField) {
     300            formData.append('saiap_notifications_nonce', nonceField.value);
     301        }
     302
     303        $.ajax({
     304            url: ajaxurl,
     305            type: 'POST',
     306            data: formData,
     307            processData: false,
     308            contentType: false,
     309            success: function(response) {
     310                if (btn) {
     311                    btn.disabled = false;
     312                    btn.textContent = 'Save Notification Settings';
     313                }
     314
     315                var messageType = response.success ? 'success' : 'error';
     316                var message = response.success
     317                    ? (response.data?.message || 'Notification settings saved successfully.')
     318                    : (response.data || 'Error saving notification settings.');
     319
     320                showNotificationsMessage(messageType, message);
     321            },
     322            error: function() {
     323                if (btn) {
     324                    btn.disabled = false;
     325                    btn.textContent = 'Save Notification Settings';
     326                }
     327                showNotificationsMessage('error', 'Server error occurred. Please try again later.');
     328            }
     329        });
     330    };
     331
     332    function showNotificationsMessage(type, message) {
     333        var msgElem = document.getElementById('notifications-settings-message');
     334        if (msgElem) {
     335            msgElem.innerHTML = '<div class="notice notice-' + type + ' inline"><p>' + message + '</p></div>';
     336            msgElem.style.display = 'block';
     337
     338            setTimeout(function() {
     339                msgElem.style.display = 'none';
     340            }, 5000);
     341        }
     342    }
     343
    285344    // Tracking consent notice handlers
    286345    $('#saiap-tracking-allow').on('click', function() {
  • clearpost-simple-ai-auto-post/trunk/assets/js/scheduler.js

    r3373464 r3387025  
    3939                            <div class="prompt-actions">
    4040                                <button class="button-secondary edit-prompt">${saiapAdmin.i18n.edit}</button>
    41                                 <button class="button-secondary regenerate-prompt">${saiapAdmin.i18n.regenerate}</button>
    4241                            </div>
    4342                        </div>
     
    101100        const noticeHtml = `
    102101            <div class="notice notice-info saiap-scheduler-premium-notice">
    103                 <p><strong>Post Scheduler - Premium Feature</strong></p>
    104                 <p>This feature allows you to schedule AI-generated posts up to 7 days in advance with automated content creation. It uses your site's context architecture saved in our premium cloud service.</p>
    105                 <p style="margin-top:8px;">Auto-generate 7 days of posts in your exact style.</p>
     102                <p><strong>Post Scheduler - Generate organic traffic on autopilot</strong></p>
     103                <p>Watch as ClearPost learns what your audience wants and schedules posts that get traffic while you sleep.</p>
     104                <p style="margin-top:8px;">Get found on Google. Get recommended by ChatGPT. On autopilot.</p>
    106105                <p>
    107                     <button class="button-primary premium-upgrade-btn">Upgrade to Premium</button>
    108                     <a class="button-link" href="https://clearpostplugin.com/#pricing" target="_blank" style="margin-left: 10px;">Learn more about Premium</a>
     106                    <a class="button-link" href="https://clearpostplugin.com/" target="_blank" style="margin-left: 10px;">Grow traffic automatically</a>
    109107                    <button class="button-secondary notice-dismiss-btn" style="margin-left: 10px;">Dismiss</button>
    110108                </p>
     
    117115        }
    118116
    119         // Handle upgrade button click
    120         $('.premium-upgrade-btn').on('click', function(e) {
    121             e.preventDefault();
    122             window.open('https://clearpostplugin.com/#pricing', '_blank');
    123         });
    124 
    125117        // Handle dismiss button
    126118        $('.notice-dismiss-btn').on('click', function(e) {
     
    145137    });
    146138
    147     // Event handler for regenerating a prompt
    148     schedulerGrid.on('click', '.regenerate-prompt', function () {
    149         const button = $(this);
    150         const card = button.closest('.prompt-card');
    151         const promptId = card.data('id');
    152 
    153         if (!promptId) {
    154             alert('Could not find prompt ID.');
    155             return;
    156         }
    157 
    158         // Add loading state
    159         card.css('opacity', '0.5');
    160         button.prop('disabled', true);
    161 
    162         $.ajax({
    163             url: saiapAdmin.ajaxurl,
    164             method: 'POST',
    165             data: {
    166                 action: 'saiap_regenerate_prompt',
    167                 nonce: saiapAdmin.schedulerNonce,
    168                 prompt_id: promptId
    169             },
    170             success: function (response) {
    171                 if (response.success) {
    172                     // Update the UI with the new prompt
    173                     card.find('.prompt-content p').text(response.data.prompt);
    174                 } else {
    175                     const errorMessage = getErrorMessage(response.data);
    176                     alert('Error regenerating prompt: ' + errorMessage);
    177                 }
    178             },
    179             error: function () {
    180                 alert('Server error while regenerating prompt.');
    181             },
    182             complete: function () {
    183                 // Remove loading state
    184                 card.css('opacity', '1');
    185                 button.prop('disabled', false);
    186             }
    187         });
    188     });
     139   
    189140
    190141    // Modal handling
  • clearpost-simple-ai-auto-post/trunk/assets/js/site-context.js

    r3373464 r3387025  
    195195        const noticeHtml = `
    196196            <div class="notice notice-info saiap-site-context-premium-notice">
    197                 <p><strong>Site Context AI Agent - Premium Feature</strong></p>
    198                 <p>This AI agent analyzes your existing content to understand your unique voice, style, and topics. It creates custom AI instructions so new posts automatically match your established style, replacing hours of manual prompt engineering.</p>
     197                <p><strong>The Secret to Posts that Rank (and Sound Like You)</strong></p>
     198                <p>This AI agent analyzes your existing content to understand your style, your audience, and your goals. It creates a custom context architecture that ensures your new posts naturally rank and sound like you.</p>
    199199                <p>
    200                     <button class="button-primary premium-upgrade-btn">Upgrade to Premium</button>
     200                    <button class="button-primary premium-upgrade-btn">Grow organic traffic on autopilot</button>
    201201                    <button class="button-secondary notice-dismiss-btn" style="margin-left: 10px;">Dismiss</button>
    202202                </p>
     
    212212        $('.premium-upgrade-btn').on('click', function(e) {
    213213            e.preventDefault();
    214             window.open('https://clearpostplugin.com/#pricing', '_blank');
     214            window.open('https://clearpostplugin.com/', '_blank');
    215215        });
    216216
  • clearpost-simple-ai-auto-post/trunk/includes/ai-requests.php

    r3373464 r3387025  
    4242    $messages[] = array( 'role' => 'user', 'content' => $user_message );
    4343
    44     // Make API call based on provider
    45     if ( $provider === 'openai' ) {
    46         $schema = array(
    47             'type'       => 'object',
    48             'properties' => array(
    49                 'chat_message'   => array(
    50                     'type'        => 'string',
    51                     'description' => 'Brief explanation of what you changed or a conversational response.',
    52                 ),
    53                 'has_edit'       => array(
    54                     'type'        => 'boolean',
    55                     'description' => 'Whether an edit was made to the content.',
    56                 ),
    57                 'edited_content' => array(
    58                     'type'        => 'string',
    59                     'description' => 'The complete edited content. If no edit is made, this should be an empty string.',
    60                 ),
    61             ),
    62             'required'   => array( 'chat_message', 'has_edit', 'edited_content' ),
    63             'additionalProperties' => false,
    64         );
    65 
    66         $response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array(
    67             'headers' => array(
    68                 'Authorization' => 'Bearer ' . $api_key,
    69                 'Content-Type' => 'application/json',
    70             ),
    71             'body' => wp_json_encode( array(
    72                 'model' => $model ?: 'gpt-5',
    73                 'messages' => $messages,
    74                 'tool_choice' => array( 'type' => 'function', 'function' => array( 'name' => 'json_responder') ),
    75                 'tools'       => array(
    76                     array(
    77                         'type' => 'function',
    78                         'function' => array(
    79                             'name' => 'json_responder',
    80                             'description' => 'Responds with a JSON object containing a chat message and edited content.',
    81                             'parameters' => $schema,
    82                             'strict' => true,
    83                         )
    84                     )
    85                 )
    86             ) ),
    87             'timeout' => 120,
    88         ) );
    89 
    90         if ( is_wp_error( $response ) ) {
    91             return $response;
    92         }
    93 
    94         $body = wp_remote_retrieve_body( $response );
    95         $data = json_decode( $body, true );
    96        
    97         if ( isset( $data['choices'][0]['message']['tool_calls'][0]['function']['arguments'] ) ) {
    98             return $data['choices'][0]['message']['tool_calls'][0]['function']['arguments'];
    99         }
    100 
    101         if ( isset( $data['error']['message'] ) ) {
    102             return new WP_Error( 'api_error', 'OpenAI API Error: ' . sanitize_text_field( $data['error']['message'] ) );
    103         }
    104        
    105         return new WP_Error( 'invalid_response', 'Invalid response from OpenAI API' );
     44    // Make API call based on provider
     45    if ( $provider === 'openai' ) {
     46        $schema = array(
     47            'type'       => 'object',
     48            'properties' => array(
     49                'chat_message'   => array(
     50                    'type'        => 'string',
     51                    'description' => 'Brief explanation of what you changed or a conversational response.',
     52                ),
     53                'has_edit'       => array(
     54                    'type'        => 'boolean',
     55                    'description' => 'Whether an edit was made to the content.',
     56                ),
     57                'edited_content' => array(
     58                    'type'        => 'string',
     59                    'description' => 'The complete edited content. If no edit is made, this should be an empty string.',
     60                ),
     61            ),
     62            'required'   => array( 'chat_message', 'has_edit', 'edited_content' ),
     63            'additionalProperties' => false,
     64        );
     65
     66        // Convert messages to Responses API input format
     67        $input = array();
     68        foreach ( $messages as $m ) {
     69            $input[] = array(
     70                'role'    => $m['role'],
     71                'content' => array(
     72                    array( 'type' => 'input_text', 'text' => $m['content'] ),
     73                ),
     74            );
     75        }
     76
     77        $response = wp_remote_post( 'https://api.openai.com/v1/responses', array(
     78            'headers' => array(
     79                'Authorization' => 'Bearer ' . $api_key,
     80                'Content-Type' => 'application/json',
     81            ),
     82            'body' => wp_json_encode( array(
     83                'model' => $model ?: 'gpt-5',
     84                'tools' => array( array( 'type' => 'web_search' ) ),
     85                'input' => $input,
     86                'text' => array(
     87                    'format' => array(
     88                        'type' => 'json_schema',
     89                        'name' => 'json_responder',
     90                        'schema' => $schema,
     91                        'strict' => true,
     92                    )
     93                ),
     94            ) ),
     95            'timeout' => 180,
     96        ) );
     97
     98        if ( is_wp_error( $response ) ) {
     99            return $response;
     100        }
     101
     102        $body = wp_remote_retrieve_body( $response );
     103        $data = json_decode( $body, true );
     104
     105        // Responses API returns output_text or output tree
     106        if ( isset( $data['output_text'] ) && is_string( $data['output_text'] ) && $data['output_text'] !== '' ) {
     107            return $data['output_text'];
     108        }
     109        if ( isset( $data['output'][0]['content'][0]['text'] ) && is_string( $data['output'][0]['content'][0]['text'] ) ) {
     110            return $data['output'][0]['content'][0]['text'];
     111        }
     112
     113        if ( isset( $data['error']['message'] ) ) {
     114            return new WP_Error( 'api_error', 'OpenAI API Error: ' . sanitize_text_field( $data['error']['message'] ) );
     115        }
     116
     117        return new WP_Error( 'invalid_response', 'Invalid response from OpenAI Responses API' );
    106118       
    107119    } elseif ( $provider === 'anthropic' ) {
     
    141153        }
    142154
    143         // Prepare request body for Anthropic
     155        // Prepare request body for Anthropic (enable web search + JSON tool)
    144156        $request_body = array(
    145157            'model'       => $model ?: 'claude-sonnet-4-20250514',
    146158            'messages'    => $anthropic_messages,
    147             'tool_choice' => array( 'type' => 'tool', 'name' => 'json_responder' ),
    148             'tools'       => array( $tool_schema ),
     159            'tool_choice' => 'auto',
     160            'tools'       => array( array( 'type' => 'web_search' ), $tool_schema ),
    149161        );
    150162
     
    188200        if ( isset( $data['content'] ) && is_array( $data['content'] ) ) {
    189201            foreach ( $data['content'] as $content_block ) {
    190                 if ( isset( $content_block['type'] ) && 'tool_use' === $content_block['type'] && isset( $content_block['input'] ) ) {
     202                if (
     203                    isset( $content_block['type'] ) &&
     204                    'tool_use' === $content_block['type'] &&
     205                    isset( $content_block['name'] ) &&
     206                    'json_responder' === $content_block['name'] &&
     207                    isset( $content_block['input'] )
     208                ) {
    191209                    return wp_json_encode( $content_block['input'] );
    192210                }
     
    548566        $content = $data['candidates'][0]['content']['parts'][0]['text'];
    549567
    550     } elseif ( $provider === 'openai' ) {
    551         // Request to OpenAI API
    552         $api_key = get_option( 'saiap_openai_api_key' );
    553 
    554         if ( empty( $api_key ) ) {
    555             throw new Exception( 'OpenAI API key is required. Please enter your API key in the settings.' );
    556         }
    557 
    558         $response = wp_remote_post(
    559             'https://api.openai.com/v1/chat/completions',
    560             array(
    561                 'headers' => array(
    562                     'Authorization' => 'Bearer ' . $api_key,
    563                     'Content-Type'  => 'application/json',
    564                 ),
    565                 'body'    => wp_json_encode( $request_data ),
    566                 'timeout' => 120,
    567             )
    568         );
    569 
    570         if ( is_wp_error( $response ) ) {
    571             throw new Exception( esc_html( $response->get_error_message() ) );
    572         }
    573 
    574         $status_code = wp_remote_retrieve_response_code( $response );
    575         if ( $status_code !== 200 ) {
    576             $error_message = 'OpenAI API error: ';
    577             $body          = wp_remote_retrieve_body( $response );
    578             $data          = json_decode( $body, true );
    579 
    580             if ( isset( $data['error']['message'] ) ) {
    581                 $error_message .= sanitize_text_field( $data['error']['message'] );
    582             } else {
    583                 $error_message .= 'HTTP status ' . $status_code;
    584             }
    585 
    586             throw new Exception( esc_html( $error_message ) );
    587         }
    588 
    589         $body = wp_remote_retrieve_body( $response );
    590         $data = json_decode( $body, true );
    591 
    592         if ( ! $data || ! isset( $data['choices'][0]['message']['content'] ) ) {
    593             throw new Exception( 'Invalid response from OpenAI API. Please check your API key and try again.' );
    594         }
    595 
    596         $content = $data['choices'][0]['message']['content'];
     568    } elseif ( $provider === 'openai' ) {
     569        // Request to OpenAI Responses API
     570        $api_key = get_option( 'saiap_openai_api_key' );
     571
     572        if ( empty( $api_key ) ) {
     573            throw new Exception( 'OpenAI API key is required. Please enter your API key in the settings.' );
     574        }
     575
     576        // Convert messages to Responses API input format
     577        $input = array();
     578        foreach ( $request_data['messages'] as $m ) {
     579            $input[] = array(
     580                'role'    => $m['role'],
     581                'content' => array(
     582                    array( 'type' => 'input_text', 'text' => $m['content'] ),
     583                ),
     584            );
     585        }
     586
     587        $response = wp_remote_post(
     588            'https://api.openai.com/v1/responses',
     589            array(
     590                'headers' => array(
     591                    'Authorization' => 'Bearer ' . $api_key,
     592                    'Content-Type'  => 'application/json',
     593                ),
     594                'body'    => wp_json_encode( array(
     595                    'model' => $request_data['model'],
     596                    'tools' => array( array( 'type' => 'web_search' ) ),
     597                    'input' => $input,
     598                ) ),
     599                'timeout' => 180,
     600            )
     601        );
     602
     603        if ( is_wp_error( $response ) ) {
     604            throw new Exception( esc_html( $response->get_error_message() ) );
     605        }
     606
     607        $status_code = wp_remote_retrieve_response_code( $response );
     608        if ( $status_code !== 200 ) {
     609            $error_message = 'OpenAI API error: ';
     610            $body          = wp_remote_retrieve_body( $response );
     611            $data          = json_decode( $body, true );
     612
     613            if ( isset( $data['error']['message'] ) ) {
     614                $error_message .= sanitize_text_field( $data['error']['message'] );
     615            } else {
     616                $error_message .= 'HTTP status ' . $status_code;
     617            }
     618
     619            throw new Exception( esc_html( $error_message ) );
     620        }
     621
     622        $body = wp_remote_retrieve_body( $response );
     623        $data = json_decode( $body, true );
     624
     625        // Responses API returns output_text or output tree
     626        if ( isset( $data['output_text'] ) && is_string( $data['output_text'] ) && $data['output_text'] !== '' ) {
     627            $content = $data['output_text'];
     628        } elseif ( isset( $data['output'][0]['content'][0]['text'] ) && is_string( $data['output'][0]['content'][0]['text'] ) ) {
     629            $content = $data['output'][0]['content'][0]['text'];
     630        } else {
     631            throw new Exception( 'Invalid response from OpenAI Responses API. Please check your API key and try again.' );
     632        }
    597633    } else {
    598634        // Unknown provider
  • clearpost-simple-ai-auto-post/trunk/includes/ai-settings.php

    r3373464 r3387025  
    9191
    9292/**
     93 * AJAX callback for saving notification settings
     94 */
     95function saiap_save_notifications_callback() {
     96    // Check nonce for security
     97    if ( ! isset( $_POST['saiap_notifications_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['saiap_notifications_nonce'] ) ), 'saiap_save_notifications_action' ) ) {
     98        wp_send_json_error( __( 'Security check failed', 'clearpost-simple-ai-auto-post' ) );
     99    }
     100
     101    if ( ! current_user_can( 'manage_options' ) ) {
     102        wp_send_json_error( __( 'Unauthorized', 'clearpost-simple-ai-auto-post' ) );
     103    }
     104
     105    $enabled    = isset( $_POST['saiap_notify_on_schedule'] ) ? sanitize_text_field( wp_unslash( $_POST['saiap_notify_on_schedule'] ) ) : 'no';
     106    $recipients = isset( $_POST['saiap_notify_recipients'] ) ? sanitize_text_field( wp_unslash( $_POST['saiap_notify_recipients'] ) ) : '';
     107
     108    update_option( 'saiap_notify_on_schedule', $enabled === 'yes' ? 'yes' : 'no' );
     109    update_option( 'saiap_notify_recipients', $recipients );
     110
     111    wp_send_json_success( array( 'message' => __( 'Notification settings saved!', 'clearpost-simple-ai-auto-post' ) ) );
     112}
     113
     114add_action( 'wp_ajax_saiap_save_notifications', 'saiap_save_notifications_callback' );
     115
     116/**
    93117 * Render the Settings tab
    94118 */
    95119function saiap_render_ai_settings_tab( $models = array() ) {
     120    $license_key = get_option( 'saiap_license_key', '' );
    96121    ?>
    97122    <div class="tab-content ai-settings-tab-content">
    98123        <div class="container">
     124            <?php if ( empty( $license_key ) ) : ?>
    99125            <div class="col col-12">
    100126                <div class="card">
    101127                    <h2 class="spacing-16-row"><?php esc_html_e( 'API Settings', 'clearpost-simple-ai-auto-post' ); ?></h2>
    102                     <p class="spacing-16-row"><?php esc_html_e( 'Enter your API keys for any providers you wish to use.', 'clearpost-simple-ai-auto-post' ); ?></p>
    103                     <p class="spacing-16-row"><?php esc_html_e( 'Note: You don\'t have to do this if you\'re using our premium cloud service.', 'clearpost-simple-ai-auto-post' ); ?></p>
     128                    <p class="spacing-16-row"><?php esc_html_e( 'Enter your API keys for any LLM providers you wish to use.', 'clearpost-simple-ai-auto-post' ); ?></p>
     129                    <p class="spacing-16-row"><?php esc_html_e( 'Note: You don\'t have to do this if you\'re using ClearPost Premium (no API keys needed).', 'clearpost-simple-ai-auto-post' ); ?></p>
    104130                    <div class="spacing-8-row">
    105                         <a href="https://clearpostplugin.com/#pricing" target="_blank" class="secondary-button"><?php esc_html_e( 'Try Premium (no API keys needed)', 'clearpost-simple-ai-auto-post' ); ?></a>
     131                        <a href="https://clearpostplugin.com/" target="_blank" class="secondary-button"><?php esc_html_e( 'Grow organic traffic on autopilot (no API keys needed)', 'clearpost-simple-ai-auto-post' ); ?></a>
    106132                    </div>
    107133                   
     
    145171                </div>
    146172            </div>
     173            <?php endif; ?>
     174
     175            <div class="col col-12">
     176                <div class="card">
     177                    <h2 class="spacing-16-row"><?php esc_html_e( 'Notifications', 'clearpost-simple-ai-auto-post' ); ?></h2>
     178                    <p class="spacing-16-row"><?php esc_html_e( 'Get an email when the scheduler creates a new draft.', 'clearpost-simple-ai-auto-post' ); ?></p>
     179
     180                    <form id="saiap-notifications-form" method="post">
     181                        <?php wp_nonce_field( 'saiap_save_notifications_action', 'saiap_notifications_nonce' ); ?>
     182
     183                        <div class="spacing-16-row">
     184                            <label for="saiap_notify_on_schedule" class="body-text"><?php esc_html_e( 'Enable email notifications', 'clearpost-simple-ai-auto-post' ); ?></label>
     185                            <select id="saiap_notify_on_schedule" name="saiap_notify_on_schedule" class="regular-text">
     186                                <option value="no" <?php selected( get_option( 'saiap_notify_on_schedule', 'no' ), 'no' ); ?>><?php esc_html_e( 'No', 'clearpost-simple-ai-auto-post' ); ?></option>
     187                                <option value="yes" <?php selected( get_option( 'saiap_notify_on_schedule', 'no' ), 'yes' ); ?>><?php esc_html_e( 'Yes', 'clearpost-simple-ai-auto-post' ); ?></option>
     188                            </select>
     189                        </div>
     190
     191                        <div class="spacing-16-row">
     192                            <label for="saiap_notify_recipients" class="body-text"><?php esc_html_e( 'Recipients (comma-separated emails)', 'clearpost-simple-ai-auto-post' ); ?></label>
     193                            <input type="text" id="saiap_notify_recipients" name="saiap_notify_recipients" class="large-text" value="<?php echo esc_attr( get_option( 'saiap_notify_recipients', get_option( 'admin_email', '' ) ) ); ?>" />
     194                            <p class="small-text"><?php esc_html_e( 'Defaults to the site admin email.', 'clearpost-simple-ai-auto-post' ); ?></p>
     195                        </div>
     196
     197                        <div class="spacing-24 text-align-center">
     198                            <button type="button" id="saiap-save-notifications" class="primary-button" onclick="saveNotificationSettings()">
     199                                <?php esc_html_e( 'Save Notification Settings', 'clearpost-simple-ai-auto-post' ); ?>
     200                            </button>
     201                        </div>
     202
     203                        <div id="notifications-settings-message" class="spacing-16" style="display: none;"></div>
     204                    </form>
     205                </div>
     206            </div>
    147207        </div>
    148208    </div>
  • clearpost-simple-ai-auto-post/trunk/includes/editor-chat.php

    r3373464 r3387025  
    6464    }
    6565
    66     // Get API key for the provider
    67     $api_key_option = 'saiap_' . $provider . '_api_key';
    68     $api_key = get_option( $api_key_option );
    69 
    70     if ( empty( $api_key ) ) {
    71         return new WP_Error( 'missing_api_key', 'API key not configured for ' . $provider );
    72     }
    73 
    74     // Create editing-specific system prompt
     66    // Premium-first: If license key exists, try premium chat endpoint before local provider
     67    $license_key = get_option( 'saiap_license_key', '' );
     68
     69    // Create editing-specific system prompt
    7570    $editing_system_prompt = "You are a friendly and helpful AI assistant and content editor for WordPress. Your goal is to help the user write and improve their blog post. You can perform edits, answer questions about the content, or just chat about the post.
    7671
     
    120115" . $current_content;
    121116
    122     // Prepare the user message
     117    // Prepare the user message
    123118    $user_message = "My request is: " . $message;
    124119
    125     // Make AI request based on provider
    126     try {
    127         $result = saiap_make_ai_request( $user_message, $provider, $model, $editing_system_prompt );
    128 
    129         if ( is_wp_error( $result ) ) {
    130             return $result;
    131         }
    132 
    133         // Parse the JSON response
    134         $parsed_response = json_decode( $result, true );
    135 
    136         // Check if the response is valid and contains the required fields
    137         if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $parsed_response ) || ! isset( $parsed_response['chat_message'] ) || ! isset( $parsed_response['edited_content'] ) ) {
    138             // If not, treat the entire response as a chat message and assume no edit was made.
    139             // This prevents overwriting content with an error or non-content response.
    140             return array(
    141                 'chat_message'   => $result, // The raw response from the AI.
    142                 'has_edit'       => false,
    143                 'edited_content' => '', // No changes to the content.
    144             );
    145         }
    146 
    147         // Sanitize the response
    148         $response = array(
    149             'chat_message' => sanitize_text_field( $parsed_response['chat_message'] ),
    150             'has_edit'     => isset( $parsed_response['has_edit'] ) ? (bool) $parsed_response['has_edit'] : true,
    151             'edited_content' => wp_kses_post( $parsed_response['edited_content'] ),
    152         );
    153 
    154         return $response;
    155 
    156     } catch ( Exception $e ) {
    157         return new WP_Error( 'api_request_failed', 'Failed to communicate with AI service: ' . $e->getMessage() );
    158     }
     120    // Attempt premium call if license is present
     121    if ( ! empty( $license_key ) ) {
     122        $domain   = function_exists( 'saiap_get_server_host' ) ? saiap_get_server_host() : wp_parse_url( home_url(), PHP_URL_HOST );
     123        $messages = array(
     124            array( 'role' => 'system', 'content' => $editing_system_prompt ),
     125            array( 'role' => 'user', 'content' => $user_message ),
     126        );
     127
     128        $premium_response = wp_remote_post(
     129            'https://saiap.gopurposego.com/api/chat/edit',
     130            array(
     131                'headers' => array(
     132                    'Content-Type' => 'application/json',
     133                ),
     134                'body'    => wp_json_encode(
     135                    array(
     136                        'license_key' => $license_key,
     137                        'domain'      => $domain,
     138                        'provider'    => $provider,
     139                        'model'       => $model,
     140                        'messages'    => $messages,
     141                    )
     142                ),
     143                'timeout' => 120,
     144            )
     145        );
     146
     147        if ( is_wp_error( $premium_response ) ) {
     148            // Network or request error — surface as request failure (no fallback)
     149            return new WP_Error( 'api_request_failed', esc_html( $premium_response->get_error_message() ) );
     150        }
     151
     152        $status_code = wp_remote_retrieve_response_code( $premium_response );
     153        $body        = wp_remote_retrieve_body( $premium_response );
     154
     155        if ( $status_code === 401 || $status_code === 403 ) {
     156            // Auth error — fall back to local provider if possible
     157        } elseif ( $status_code !== 200 ) {
     158            // Other server/provider error — surface without fallback
     159            $data = json_decode( $body, true );
     160            $msg  = isset( $data['error'] ) ? sanitize_text_field( $data['error'] ) : 'Premium service error';
     161            return new WP_Error( 'api_error', $msg );
     162        } else {
     163            // 200 OK — parse structured response
     164            $data = json_decode( $body, true );
     165            if ( isset( $data['chat_message'] ) && isset( $data['edited_content'] ) && isset( $data['has_edit'] ) ) {
     166                return array(
     167                    'chat_message'   => sanitize_text_field( $data['chat_message'] ),
     168                    'has_edit'       => (bool) $data['has_edit'],
     169                    'edited_content' => wp_kses_post( $data['edited_content'] ),
     170                );
     171            }
     172
     173            // Invalid structured response
     174            return new WP_Error( 'invalid_response', 'Invalid response from premium chat API' );
     175        }
     176    }
     177
     178    // Fallback to local provider flow (or primary when no license)
     179    // Get API key for the provider
     180    $api_key_option = 'saiap_' . $provider . '_api_key';
     181    $api_key = get_option( $api_key_option );
     182
     183    if ( empty( $api_key ) ) {
     184        return new WP_Error( 'missing_api_key', 'API key not configured for ' . $provider );
     185    }
     186
     187    // Make AI request based on provider
     188    try {
     189        $result = saiap_make_ai_request( $user_message, $provider, $model, $editing_system_prompt );
     190
     191        if ( is_wp_error( $result ) ) {
     192            return $result;
     193        }
     194
     195        // Parse the JSON response
     196        $parsed_response = json_decode( $result, true );
     197
     198        // Check if the response is valid and contains the required fields
     199        if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $parsed_response ) || ! isset( $parsed_response['chat_message'] ) || ! isset( $parsed_response['edited_content'] ) ) {
     200            // If not, treat the entire response as a chat message and assume no edit was made.
     201            // This prevents overwriting content with an error or non-content response.
     202            return array(
     203                'chat_message'   => $result, // The raw response from the AI.
     204                'has_edit'       => false,
     205                'edited_content' => '', // No changes to the content.
     206            );
     207        }
     208
     209        // Sanitize the response
     210        $response = array(
     211            'chat_message' => sanitize_text_field( $parsed_response['chat_message'] ),
     212            'has_edit'     => isset( $parsed_response['has_edit'] ) ? (bool) $parsed_response['has_edit'] : true,
     213            'edited_content' => wp_kses_post( $parsed_response['edited_content'] ),
     214        );
     215
     216        return $response;
     217
     218    } catch ( Exception $e ) {
     219        return new WP_Error( 'api_request_failed', 'Failed to communicate with AI service: ' . $e->getMessage() );
     220    }
    159221}
    160222
     
    176238
    177239    $post_id = 0;
     240    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only access to current editor screen context
    178241    if ( isset( $_GET['post'] ) ) {
    179242        $post_id = intval( $_GET['post'] );
     
    187250            $post_type = $post->post_type;
    188251        }
     252    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only post_type detection in editor context
    189253    } elseif ( isset( $_GET['post_type'] ) ) {
    190254        $post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
  • clearpost-simple-ai-auto-post/trunk/includes/generate.php

    r3373464 r3387025  
    144144            }
    145145        }
    146     } catch ( Exception $e ) {
    147         if ( function_exists( 'error_log' ) ) {
    148             error_log( '[SAIAP] Auto-image failed: ' . $e->getMessage() );
    149         }
    150         do_action( 'saiap_auto_image_failed', $post_id, new WP_Error( 'auto_image_exception', $e->getMessage() ) );
    151     }
     146    } catch ( Exception $e ) {
     147        do_action( 'saiap_auto_image_failed', $post_id, new WP_Error( 'auto_image_exception', $e->getMessage() ) );
     148    }
    152149
    153150    // Return success data
     
    450447                        <div class="notice notice-info inline">
    451448                            <p class="small-text" style="margin: 8px 0;">
    452                                 <?php esc_html_e( 'Skip provider setup and generate instantly with ClearPost Premium (no API keys needed).', 'clearpost-simple-ai-auto-post' ); ?>
    453                                 <a href="https://clearpostplugin.com/#pricing" target="_blank"><?php esc_html_e( 'Learn more', 'clearpost-simple-ai-auto-post' ); ?></a>
     449                                <?php esc_html_e( 'Grow organic traffic on autopilot while you sleep - with ClearPost Premium.', 'clearpost-simple-ai-auto-post' ); ?>
     450                                <a href="https://clearpostplugin.com/" target="_blank"><?php esc_html_e( 'Generate traffic on autopilot', 'clearpost-simple-ai-auto-post' ); ?></a>
    454451                            </p>
    455452                            <p style="margin: 8px 0;">
  • clearpost-simple-ai-auto-post/trunk/includes/images.php

    r3373464 r3387025  
    169169
    170170    $file_array = array(
    171         'name'     => basename( parse_url( $imageUrl, PHP_URL_PATH ) ) ?: 'image.jpg',
     171        'name'     => basename( wp_parse_url( $imageUrl, PHP_URL_PATH ) ) ?: 'image.jpg',
    172172        'tmp_name' => $tmp,
    173173    );
    174174
    175175    $overrides = array( 'test_form' => false );
    176     $results   = wp_handle_sideload( $file_array, $overrides );
    177     if ( isset( $results['error'] ) ) {
    178         @unlink( $tmp );
    179         return new WP_Error( 'sideload_failed', $results['error'] );
    180     }
     176    $results   = wp_handle_sideload( $file_array, $overrides );
     177    if ( isset( $results['error'] ) ) {
     178        wp_delete_file( $tmp );
     179        return new WP_Error( 'sideload_failed', $results['error'] );
     180    }
    181181
    182182    $filename    = $results['file'];
  • clearpost-simple-ai-auto-post/trunk/includes/licensing.php

    r3357104 r3387025  
    130130
    131131    ?>
     132    <?php if ( empty( $license_key ) ) : ?>
    132133    <div class="container">
    133134        <div class="card">
    134             <h3 class="spacing-16-row"><?php esc_html_e( 'AI that knows your voice and goals', 'clearpost-simple-ai-auto-post' ); ?></h3>
     135            <h3 class="spacing-16-row"><?php esc_html_e( 'Grow organic traffic on autopilot', 'clearpost-simple-ai-auto-post' ); ?></h3>
    135136           
    136137            <div class="row">
     
    138139                    <p class="body-text spacing-16-row">
    139140                    <?php
    140                         esc_html_e( 'Go further, faster with our Site Context Agent:', 'clearpost-simple-ai-auto-post' );
     141                        esc_html_e( 'Finally get found on Google. Get recommended by ChatGPT. Get the content engine that runs while you sleep.', 'clearpost-simple-ai-auto-post' );
    141142                    ?>
    142143                    </p>
    143144                    <ul>
    144                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Site context agent: Indexes and vectorizes your existing posts to understand your unique voice, formatting patterns, style, and topics.', 'clearpost-simple-ai-auto-post' ); ?></li>
    145                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Autonomous post scheduling: Get prompts and posts generating automatically while you sleep.', 'clearpost-simple-ai-auto-post' ); ?></li>
    146                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Enhanced AI chat: Your post editor copilot understands your full site context.', 'clearpost-simple-ai-auto-post' ); ?></li>
    147                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Your style, reproduced effortlessly: The agent creates custom AI instructions based on your site\'s existing content, so new posts automatically match your established style. Replaces hours of frustrating prompt engineering.', 'clearpost-simple-ai-auto-post' ); ?></li>
    148                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Maintain control: You can edit the AI-created system prompts for fine-grained control over how new posts come out.', 'clearpost-simple-ai-auto-post' ); ?></li>
    149                         <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Predictable price for AI usage: For a simple, predictable monthly subscription, with no nasty surprises from AI provider invoices.', 'clearpost-simple-ai-auto-post' ); ?></li>
     145                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Relax while your AI content engine pumps out content that drives organic traffic to your site.', 'clearpost-simple-ai-auto-post' ); ?></li>
     146                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Context + SEO AI Agent deeply analyzes your site, target audience, and objectives to produce a context architecture that ensures your new posts naturally rank and sound like you.', 'clearpost-simple-ai-auto-post' ); ?></li>
     147                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ ClearPost autonomously schedules and generates articles every day, then pings you to review and publish.', 'clearpost-simple-ai-auto-post' ); ?></li>
     148                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ AI Chat-to-Edit: Just chat with the AI and watch it edit your posts in real time.', 'clearpost-simple-ai-auto-post' ); ?></li>                     
     149                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Auto Google Image Search: Our AI does a Google Image search for you, and suggests images for every post.', 'clearpost-simple-ai-auto-post' ); ?></li>
     150                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ AI that lives in your site: ClearPost is WordPress-native, and always learning and getting smarter - so your content gets better and better over time.', 'clearpost-simple-ai-auto-post' ); ?></li>
    150151                        <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Premium support', 'clearpost-simple-ai-auto-post' ); ?></li>
    151152                    </ul>
     
    161162                       
    162163                        <p class="small-text" style="margin: 16px 0 8px 0;">
    163                             <?php esc_html_e( 'Imagine a simple way to use AI to accelerate your workflow. Drop a prompt, click Generate, and watch as Gutenberg blocks fill up with great content that fits your style and voice. Set up a week\'s worth of drafts in a coffee break.', 'clearpost-simple-ai-auto-post' ); ?>
     164                            <?php esc_html_e( 'Imagine the impact on your business when you start publishing great content every single day.', 'clearpost-simple-ai-auto-post' ); ?>
    164165                        </p>
    165166                       
     
    177178                               
    178179                    <div class="spacing-24">
    179                         <a href="https://clearpostplugin.com/#pricing" target="_blank" class="primary-button" style="display: inline-block; text-decoration: none;">
    180                             <?php esc_html_e( 'Upgrade Now', 'clearpost-simple-ai-auto-post' ); ?>
     180                        <a href="https://clearpostplugin.com/" target="_blank" class="primary-button" style="display: inline-block; text-decoration: none;">
     181                            <?php esc_html_e( 'Grow organic traffic on autopilot', 'clearpost-simple-ai-auto-post' ); ?>
    181182                        </a>
    182183                    </div>
     
    186187        </div>
    187188    </div>
     189    <?php endif; ?>
    188190    <div class="container">
    189191        <div class="card">
     
    234236                        <p class="small-text" style="margin: 0;">
    235237                            <?php esc_html_e( 'To cancel your subscription, please email ', 'clearpost-simple-ai-auto-post' ); ?>
    236                             <a href="mailto:support@gopurposego.com">support@gopurposego.com</a>
     238                            <a href="mailto:support@clearpostplugin.com">support@clearpostplugin.com</a>
    237239                        </p>
    238240                    </div>
  • clearpost-simple-ai-auto-post/trunk/includes/onboarding.php

    r3373464 r3387025  
    1919    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    2020    if ( is_admin() && isset( $_GET['page'] ) && sanitize_text_field( wp_unslash( $_GET['page'] ) ) === 'saiap' ) {
    21         add_action( 'admin_notices', 'saiap_render_onboarding_checklist' );
     21        // Do not show onboarding for premium users (license key present)
     22        $license_key = get_option( 'saiap_license_key', '' );
     23        if ( empty( $license_key ) ) {
     24            add_action( 'admin_notices', 'saiap_render_onboarding_checklist' );
     25        }
    2226    }
    2327
     
    102106 */
    103107function saiap_render_onboarding_checklist() {
     108    // Do not render onboarding for premium users (license key present)
     109    $license_key = get_option( 'saiap_license_key', '' );
     110    if ( ! empty( $license_key ) ) {
     111        return;
     112    }
     113
    104114    // Don't show if dismissed or complete
    105115    if ( saiap_onboarding_is_dismissed() || saiap_onboarding_is_complete() ) {
     
    130140                <h3><?php esc_html_e( 'Get Started with ClearPost Simple AI Auto Post', 'clearpost-simple-ai-auto-post' ); ?></h3>
    131141                <p><?php esc_html_e( 'Complete these steps to start generating content with AI:', 'clearpost-simple-ai-auto-post' ); ?></p>
    132             </div>
    133            
    134             <div class="saiap-onboarding-progress">
    135                 <div class="saiap-progress-bar">
    136                     <div class="saiap-progress-fill" style="width: <?php echo esc_attr( $progress_percentage ); ?>%"></div>
    137                 </div>
    138                 <span class="saiap-progress-text"><?php echo esc_html( $completed_steps ); ?>/3 <?php esc_html_e( 'steps completed', 'clearpost-simple-ai-auto-post' ); ?></span>
    139142            </div>
    140143           
     
    152155                        <p><?php esc_html_e( 'Configure your OpenAI, Anthropic, or Google Gemini API key to enable AI content generation.', 'clearpost-simple-ai-auto-post' ); ?></p>
    153156                        <?php if ( ! $step1_complete ) : ?>
    154                             <a href="?page=saiap&tab=ai-settings" class="saiap-step-link"><?php esc_html_e( 'Add API Key →', 'clearpost-simple-ai-auto-post' ); ?></a>
    155                             <div class="spacing-8">
    156                                 <a href="https://clearpostplugin.com/#pricing" target="_blank" rel="noopener" class="saiap-step-link">
    157                                     <?php esc_html_e( 'Or try Premium (no API keys needed)', 'clearpost-simple-ai-auto-post' ); ?>
    158                                 </a>
    159                             </div>
     157                        <a href="?page=saiap&tab=ai-settings" class="button button-primary saiap-step-primary-action"><?php esc_html_e( 'Add API Key', 'clearpost-simple-ai-auto-post' ); ?></a>
    160158                        <?php endif; ?>
    161159                    </div>
     
    198196           
    199197            <div class="saiap-onboarding-help">
    200                 <!-- TODO: Update this link to the new getting started guide -->
    201                 <p><?php esc_html_e( 'Need help?', 'clearpost-simple-ai-auto-post' ); ?> <a href="#" class="saiap-help-link" target="_blank"><?php esc_html_e( 'View our getting started guide', 'clearpost-simple-ai-auto-post' ); ?></a></p>
     198                <p><?php esc_html_e( 'Need help?', 'clearpost-simple-ai-auto-post' ); ?> <a href="https://clearpostplugin.com/getting-started-with-clearpost-wordpress-plugin/" class="saiap-help-link" target="_blank"><?php esc_html_e( 'View our getting started guide', 'clearpost-simple-ai-auto-post' ); ?></a></p>
     199                <p class="saiap-onboarding-upsell-footnote"><a href="https://clearpostplugin.com/" target="_blank" rel="noopener noreferrer" class="saiap-help-link button button-primary button-hero"><?php esc_html_e( 'Skip the setup and start generating traffic on autopilot', 'clearpost-simple-ai-auto-post' ); ?></a></p>
    202200            </div>
    203201        </div>
     
    239237    // Only load on plugin settings page
    240238    if ( $hook !== 'toplevel_page_saiap' ) {
     239        return;
     240    }
     241
     242    // Do not load onboarding assets for premium users (license key present)
     243    $license_key = get_option( 'saiap_license_key', '' );
     244    if ( ! empty( $license_key ) ) {
    241245        return;
    242246    }
  • clearpost-simple-ai-auto-post/trunk/includes/scheduler.php

    r3373464 r3387025  
    510510            if ( is_wp_error( $result ) ) {
    511511                $status_to_set = 'failed';
     512                /**
     513                 * Fires when scheduled post generation fails.
     514                 *
     515                 * @since 1.1.9
     516                 * @param array $prompt Prompt payload
     517                 * @param WP_Error $error Error object
     518                 */
     519                do_action( 'saiap_scheduler_post_generation_failed', $prompt, $result );
     520            } else {
     521                // On success, optionally notify via email
     522                $notify = get_option( 'saiap_notify_on_schedule', 'no' );
     523                if ( 'yes' === $notify && ! empty( $result['post_id'] ) ) {
     524                    $post_id   = absint( $result['post_id'] );
     525                    $post      = get_post( $post_id );
     526                    $site_name = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
     527                    $edit_url  = get_edit_post_link( $post_id, '' );
     528                    if ( empty( $edit_url ) ) {
     529                        // Cron may lack a user context; fall back to admin edit URL which will prompt login if needed
     530                        $edit_url = admin_url( 'post.php?post=' . $post_id . '&action=edit' );
     531                    }
     532
     533                    // Build recipients from option (CSV), sanitize, dedupe
     534                    $raw_recipients = get_option( 'saiap_notify_recipients', get_option( 'admin_email', '' ) );
     535                    $emails = array_filter( array_map( 'trim', explode( ',', (string) $raw_recipients ) ) );
     536                    $emails = array_unique( array_values( array_filter( $emails, 'is_email' ) ) );
     537
     538                    if ( ! empty( $emails ) ) {
     539                        // Subject and body with i18n
     540                        $subject = sprintf(
     541                            /* translators: %s: post title */
     542                            __( 'ClearPost: New AI draft — %s', 'clearpost-simple-ai-auto-post' ),
     543                            $post ? $post->post_title : __( '(untitled)', 'clearpost-simple-ai-auto-post' )
     544                        );
     545
     546                        // Created at in site timezone
     547                        $tz      = wp_timezone();
     548                        $created = new DateTime( 'now', $tz );
     549                        $created_str = $created->format( 'Y-m-d H:i' );
     550
     551                        $body_lines = array(
     552                            /* translators: %s: site name */
     553                            sprintf( __( 'Site: %s', 'clearpost-simple-ai-auto-post' ), $site_name ),
     554                            /* translators: %s: post title */
     555                            sprintf( __( 'Title: %s', 'clearpost-simple-ai-auto-post' ), $post ? $post->post_title : '' ),
     556                            /* translators: %s: post status */
     557                            sprintf( __( 'Status: %s', 'clearpost-simple-ai-auto-post' ), __( 'Draft', 'clearpost-simple-ai-auto-post' ) ),
     558                            /* translators: %s: created date/time */
     559                            sprintf( __( 'Created: %s', 'clearpost-simple-ai-auto-post' ), $created_str ),
     560                            /* translators: %s: edit URL */
     561                            sprintf( __( 'Edit link: %s', 'clearpost-simple-ai-auto-post' ), $edit_url ),
     562                            '',
     563                            __( 'This draft was generated by ClearPost. Review, edit, and publish when ready.', 'clearpost-simple-ai-auto-post' ),
     564                        );
     565
     566                        $email_payload = array(
     567                            'recipients' => $emails,
     568                            'subject'    => $subject,
     569                            'body'       => implode( "\n", $body_lines ),
     570                            'headers'    => array( 'Content-Type: text/plain; charset=UTF-8' ),
     571                        );
     572
     573                        /**
     574                         * Filter the scheduler notification email payload.
     575                         *
     576                         * @since 1.1.9
     577                         * @param array $email_payload { recipients, subject, body, headers }
     578                         * @param int $post_id Generated post ID
     579                         * @param array $prompt Prompt payload
     580                         */
     581                        $email_payload = apply_filters( 'saiap_scheduler_notification_email', $email_payload, $post_id, $prompt );
     582
     583                        if ( ! empty( $email_payload['recipients'] ) && ! empty( $email_payload['subject'] ) && isset( $email_payload['body'] ) ) {
     584                            // Send the email (one message, multiple recipients)
     585                            wp_mail( (array) $email_payload['recipients'], (string) $email_payload['subject'], (string) $email_payload['body'], (array) ( $email_payload['headers'] ?? array() ) );
     586                        }
     587                    }
     588                }
     589
     590                /**
     591                 * Fires when a scheduled post was generated successfully.
     592                 *
     593                 * @since 1.1.9
     594                 * @param array $prompt Prompt payload
     595                 * @param array $result Result from orchestrator (post_id, edit_url)
     596                 */
     597                do_action( 'saiap_scheduler_post_generated', $prompt, $result );
    512598            }
    513599
    514600        } catch ( Exception $e ) {
    515601            $status_to_set = 'failed';
     602            do_action( 'saiap_scheduler_post_generation_failed', $prompt, new WP_Error( 'scheduler_exception', $e->getMessage() ) );
    516603        }
    517604
  • clearpost-simple-ai-auto-post/trunk/includes/site-context.php

    r3373464 r3387025  
    3838            <div class="col col-12">
    3939                <div class="card spacing-24">
    40                     <h2 class="spacing-16-row"><?php esc_html_e( 'Site Context Agent', 'clearpost-simple-ai-auto-post' ); ?></h2>
     40                    <h2 class="spacing-16-row"><?php esc_html_e( 'Context + SEO AI Agent', 'clearpost-simple-ai-auto-post' ); ?></h2>
    4141                   
    4242                    <div class="spacing-16">
    4343                        <p class="body-text">
    44                             <?php esc_html_e( 'The AI Site Context Agent uses our cloud service to analyze your posts, generates vector embeddings, and creates a context architecture to automatically create 10x more relevant content that matches your site\'s existing style and topics.', 'clearpost-simple-ai-auto-post' ); ?>
     44                            <?php esc_html_e( 'The Context + SEO AI Agent autonomously learns your site, runs SEO analysis, finds the best keywords in your niche, and maintains a context architecture to create 10x more relevant content that naturally ranks.', 'clearpost-simple-ai-auto-post' ); ?>
    4545                        </p>
    4646                       
     
    352352            $taxonomy_data = array();
    353353
    354             error_log( '[TAXONOMY DEBUG] Processing post ID ' . $post->ID . ' - Title: "' . $post->post_title . '"' );
    355             error_log( '[TAXONOMY DEBUG] Post type: ' . $post_type );
    356             error_log( '[TAXONOMY DEBUG] Available taxonomies: ' . print_r( array_keys( $all_taxonomies ), true ) );
     354           
    357355
    358356            foreach ( $all_taxonomies as $tax_slug => $taxonomy ) {
    359                 error_log( '[TAXONOMY DEBUG] Processing taxonomy: ' . $tax_slug . ' (hierarchical: ' . ( $taxonomy->hierarchical ? 'true' : 'false' ) . ')' );
     357               
    360358               
    361359                $terms = wp_get_post_terms( $post->ID, $tax_slug, array( 'fields' => 'all' ) );
    362360
    363                 error_log( '[TAXONOMY DEBUG] Raw terms for ' . $tax_slug . ': ' . print_r( $terms, true ) );
     361               
    364362
    365363                if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
     
    381379                    );
    382380                   
    383                     error_log( '[TAXONOMY DEBUG] Raw term data for ' . $tax_slug . ': ' . print_r( $raw_term_data, true ) );
     381                   
    384382                } else {
    385                     error_log( '[TAXONOMY DEBUG] No terms found for ' . $tax_slug . ' or error occurred' );
     383                   
    386384                }
    387385            }
    388386
    389             error_log( '[TAXONOMY DEBUG] Final taxonomy data for post ' . $post->ID . ': ' . print_r( $taxonomy_data, true ) );
     387           
    390388           
    391389            $formatted_posts[] = array(
     
    491489        );
    492490       
    493         error_log( '[TAXONOMY DEBUG] Sending ' . count( $posts_for_api ) . ' posts to API. Sample post data: ' . print_r( array_slice( $posts_for_api, 0, 2 ), true ) );
     491       
    494492
    495493        $response = wp_remote_post(
  • clearpost-simple-ai-auto-post/trunk/includes/taxonomy.php

    r3373464 r3387025  
    2727 */
    2828function saiap_apply_post_taxonomy( $post_id, $taxonomy_suggestions = array() ) {
    29     error_log( '[TAXONOMY APPLY DEBUG] Starting taxonomy application for post ID: ' . $post_id );
    30     error_log( '[TAXONOMY APPLY DEBUG] Taxonomy suggestions received: ' . print_r( $taxonomy_suggestions, true ) );
    3129   
    3230    if ( ! $post_id || ! is_numeric( $post_id ) ) {
     
    4442    if ( ! empty( $taxonomy_suggestions ) && is_array( $taxonomy_suggestions ) ) {
    4543        foreach ( $taxonomy_suggestions as $tax_slug => $terms ) {
    46             error_log( '[TAXONOMY APPLY DEBUG] Processing taxonomy: ' . $tax_slug . ' with terms: ' . print_r( $terms, true ) );
    47            
    48             if ( empty( $terms ) ) {
    49                 error_log( '[TAXONOMY APPLY DEBUG] Skipping empty terms for ' . $tax_slug );
    50                 continue;
    51             }
     44           
     45           
     46            if ( empty( $terms ) ) {
     47                continue;
     48            }
    5249            $taxonomy_obj = get_taxonomy( $tax_slug );
    53             if ( ! $taxonomy_obj ) {
    54                 error_log( '[TAXONOMY APPLY DEBUG] Taxonomy object not found for ' . $tax_slug );
    55                 continue;
    56             }
    57            
    58             error_log( '[TAXONOMY APPLY DEBUG] Taxonomy ' . $tax_slug . ' is hierarchical: ' . ( $taxonomy_obj->hierarchical ? 'true' : 'false' ) );
     50            if ( ! $taxonomy_obj ) {
     51                continue;
     52            }
     53           
     54           
    5955           
    6056            $term_ids = array();
    6157           
    6258            // Handle raw terms from AI
    63             if ( is_array( $terms ) && isset( $terms[0] ) && is_array( $terms[0] ) && isset( $terms[0]['name'] ) ) {
    64                 error_log( '[TAXONOMY APPLY DEBUG] Processing raw term data format for ' . $tax_slug );
     59            if ( is_array( $terms ) && isset( $terms[0] ) && is_array( $terms[0] ) && isset( $terms[0]['name'] ) ) {
    6560               
    6661                // First pass: Create all terms and build a map of suggested term_id to actual term_id
     
    7671                    }
    7772
    78                     error_log( '[TAXONOMY APPLY DEBUG] Processing term: ' . $term_name . ' (suggested parent: ' . $suggested_parent . ')' );
     73                   
    7974                   
    8075                    // Check if term exists by name
     
    8681                            // For hierarchical taxonomies, we need to find the actual parent term_id
    8782                            $actual_parent_id = isset( $term_id_map[ $suggested_parent ] ) ? $term_id_map[ $suggested_parent ] : 0;
    88                             if ( $actual_parent_id > 0 ) {
    89                                 $term_args['parent'] = $actual_parent_id;
    90                                 error_log( '[TAXONOMY APPLY DEBUG] Setting parent for ' . $term_name . ' to ' . $actual_parent_id );
    91                             }
     83                            if ( $actual_parent_id > 0 ) {
     84                                $term_args['parent'] = $actual_parent_id;
     85                            }
    9286                        }
    9387                       
    9488                        $term_result = wp_insert_term( $term_name, $tax_slug, $term_args );
    95                         if ( ! is_wp_error( $term_result ) ) {
     89                        if ( ! is_wp_error( $term_result ) ) {
    9690                            $actual_term_id = $term_result['term_id'];
    9791                            $term_ids[] = $actual_term_id;
     
    9993                            $suggested_term_id = isset( $term_data['term_id'] ) ? intval( $term_data['term_id'] ) : count( $term_id_map ) + 1;
    10094                            $term_id_map[ $suggested_term_id ] = $actual_term_id;
    101                             error_log( '[TAXONOMY APPLY DEBUG] Created new term: ' . $term_name . ' (ID: ' . $actual_term_id . ')' );
    10295                        } else {
    10396                            $errors[] = $term_result->get_error_message();
    104                             error_log( '[TAXONOMY APPLY DEBUG] Error creating term ' . $term_name . ': ' . $term_result->get_error_message() );
    10597                        }
    10698                    } else {
     
    108100                        $actual_term_id = $existing_term->term_id;
    109101                        $term_ids[] = $actual_term_id;
    110                         // Map the suggested term_id to the actual term_id
     102                        // Map the suggested term_id to the actual term_id
    111103                        $suggested_term_id = isset( $term_data['term_id'] ) ? intval( $term_data['term_id'] ) : count( $term_id_map ) + 1;
    112104                        $term_id_map[ $suggested_term_id ] = $actual_term_id;
    113                         error_log( '[TAXONOMY APPLY DEBUG] Found existing term: ' . $term_name . ' (ID: ' . $actual_term_id . ')' );
     105                       
    114106                    }
    115107                }
     
    127119                           
    128120                            if ( $actual_term_id > 0 && $actual_parent_id > 0 ) {
    129                                 $update_result = wp_update_term( $actual_term_id, $tax_slug, array( 'parent' => $actual_parent_id ) );
    130                                 if ( ! is_wp_error( $update_result ) ) {
    131                                     error_log( '[TAXONOMY APPLY DEBUG] Updated parent for term ' . $term_name . ' (ID: ' . $actual_term_id . ') to parent ID: ' . $actual_parent_id );
    132                                 } else {
    133                                     error_log( '[TAXONOMY APPLY DEBUG] Error updating parent for term ' . $term_name . ': ' . $update_result->get_error_message() );
    134                                 }
     121                                $update_result = wp_update_term( $actual_term_id, $tax_slug, array( 'parent' => $actual_parent_id ) );
     122                                if ( is_wp_error( $update_result ) ) {
     123                                    // collect error silently
     124                                }
    135125                            }
    136126                        }
     
    139129            }
    140130           
    141             if ( ! empty( $term_ids ) ) {
    142                 $all_term_ids[ $tax_slug ] = $term_ids;
    143                 error_log( '[TAXONOMY APPLY DEBUG] Final term IDs for ' . $tax_slug . ': ' . print_r( $term_ids, true ) );
    144             }
    145         }
    146     }
    147 
    148     error_log( '[TAXONOMY APPLY DEBUG] All term IDs to apply: ' . print_r( $all_term_ids, true ) );
     131            if ( ! empty( $term_ids ) ) {
     132                $all_term_ids[ $tax_slug ] = $term_ids;
     133            }
     134        }
     135    }
     136
     137   
    149138
    150139    $success_count = 0;
    151140    foreach ( $all_term_ids as $tax_slug => $term_ids_to_apply ) {
    152         error_log( '[TAXONOMY APPLY DEBUG] Applying ' . count( $term_ids_to_apply ) . ' terms to ' . $tax_slug );
    153141        $result = wp_set_post_terms( $post_id, $term_ids_to_apply, $tax_slug, true ); // Append terms
    154         if ( ! is_wp_error( $result ) ) {
     142        if ( ! is_wp_error( $result ) ) {
    155143            $success_count += count( $term_ids_to_apply );
    156             error_log( '[TAXONOMY APPLY DEBUG] Successfully applied terms to ' . $tax_slug );
    157144        } else {
    158145            $errors[] = $result->get_error_message();
    159             error_log( '[TAXONOMY APPLY DEBUG] Error applying terms to ' . $tax_slug . ': ' . $result->get_error_message() );
    160         }
    161     }
    162 
    163     error_log( '[TAXONOMY APPLY DEBUG] Application complete. Success count: ' . $success_count . ', Errors: ' . print_r( $errors, true ) );
     146        }
     147    }
     148
     149   
    164150
    165151    if ( ! empty( $errors ) ) {
     
    184170 */
    185171function saiap_create_nested_terms( $terms, $tax_slug, $parent_id, &$term_ids, &$errors ) {
    186     error_log( '[TAXONOMY NESTED DEBUG] Creating nested terms for taxonomy: ' . $tax_slug . ', parent_id: ' . $parent_id );
    187     error_log( '[TAXONOMY NESTED DEBUG] Terms to process: ' . print_r( $terms, true ) );
    188172   
    189173    foreach ( $terms as $key => $value ) {
     
    191175        $children = is_string( $key ) ? $value : array();
    192176
    193         error_log( '[TAXONOMY NESTED DEBUG] Processing term: ' . $term_name . ' (key: ' . $key . ')' );
    194         error_log( '[TAXONOMY NESTED DEBUG] Children for ' . $term_name . ': ' . print_r( $children, true ) );
     177       
    195178
    196179        if ( is_array( $term_name ) ) {
    197180            // Handle cases where the array is not associative
    198             error_log( '[TAXONOMY NESTED DEBUG] Term name is array, processing sub-items' );
     181           
    199182            foreach ( $term_name as $sub_key => $sub_value ) {
    200183                saiap_create_nested_terms( array( $sub_key => $sub_value ), $tax_slug, $parent_id, $term_ids, $errors );
     
    204187
    205188        $term_name = trim( $term_name );
    206         if ( empty( $term_name ) ) {
    207             error_log( '[TAXONOMY NESTED DEBUG] Skipping empty term name' );
    208             continue;
    209         }
    210 
    211         error_log( '[TAXONOMY NESTED DEBUG] Looking for existing term: ' . $term_name . ' in taxonomy: ' . $tax_slug );
     189        if ( empty( $term_name ) ) {
     190            continue;
     191        }
     192
    212193        $term = get_term_by( 'name', $term_name, $tax_slug );
    213194        if ( ! $term ) {
    214             error_log( '[TAXONOMY NESTED DEBUG] Term not found, creating new term: ' . $term_name . ' with parent_id: ' . $parent_id );
    215195            $term_result = wp_insert_term( $term_name, $tax_slug, array( 'parent' => $parent_id ) );
    216196            if ( ! is_wp_error( $term_result ) ) {
    217197                $current_term_id = $term_result['term_id'];
    218                 error_log( '[TAXONOMY NESTED DEBUG] Successfully created term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );
    219198            } else {
    220199                $errors[] = "Failed to create term '{$term_name}': " . $term_result->get_error_message();
    221                 error_log( '[TAXONOMY NESTED DEBUG] Error creating term ' . $term_name . ': ' . $term_result->get_error_message() );
    222200                continue;
    223201            }
    224202        } else {
    225203            $current_term_id = $term['term_id'];
    226             error_log( '[TAXONOMY NESTED DEBUG] Found existing term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );
    227204        }
    228205
    229206        $term_ids[] = $current_term_id;
    230207
    231         if ( ! empty( $children ) && is_array( $children ) ) {
    232             error_log( '[TAXONOMY NESTED DEBUG] Processing children for term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );
     208        if ( ! empty( $children ) && is_array( $children ) ) {
    233209            saiap_create_nested_terms( $children, $tax_slug, $current_term_id, $term_ids, $errors );
    234210        } else {
    235             error_log( '[TAXONOMY NESTED DEBUG] No children for term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );
    236         }
    237     }
    238    
    239     error_log( '[TAXONOMY NESTED DEBUG] Completed nested terms creation for taxonomy: ' . $tax_slug . ', parent_id: ' . $parent_id . ', term_ids: ' . print_r( $term_ids, true ) );
     211           
     212        }
     213    }
     214   
    240215}
    241216
     
    279254    }
    280255
    281     error_log( '[TAXONOMY API DEBUG] Sending taxonomy analysis request for post ID: ' . $post_id );
    282     error_log( '[TAXONOMY API DEBUG] Post content length: ' . strlen( $post_content ) );
    283     error_log( '[TAXONOMY API DEBUG] Available taxonomies: ' . print_r( $available_taxonomies, true ) );
     256   
    284257
    285258    // Attempt to get taxonomy_settings from cached context or fetch fresh
     
    290263    }
    291264    if ( is_array( $cached_context ) && isset( $cached_context['taxonomy_settings'] ) && ! empty( $cached_context['taxonomy_settings'] ) ) {
    292         $taxonomy_settings = $cached_context['taxonomy_settings'];
    293         error_log( '[TAXONOMY API DEBUG] Using cached taxonomy_settings' );
    294     }
    295     if ( empty( $taxonomy_settings ) ) {
    296         error_log( '[TAXONOMY API DEBUG] taxonomy_settings not in cache; fetching from /api/context' );
     265        $taxonomy_settings = $cached_context['taxonomy_settings'];
     266    }
     267    if ( empty( $taxonomy_settings ) ) {
    297268        $context_response = wp_remote_post(
    298269            'https://saiap.gopurposego.com/api/context',
     
    316287            $context_data   = json_decode( $context_body, true );
    317288            if ( 200 === $context_status && isset( $context_data['context']['taxonomy_settings'] ) && ! empty( $context_data['context']['taxonomy_settings'] ) ) {
    318                 $taxonomy_settings = $context_data['context']['taxonomy_settings'];
    319                 error_log( '[TAXONOMY API DEBUG] Retrieved taxonomy_settings from /api/context' );
     289                $taxonomy_settings = $context_data['context']['taxonomy_settings'];
    320290            }
    321291        }
     
    329299    if ( ! empty( $taxonomy_settings ) ) {
    330300        $payload['taxonomy_settings'] = is_array( $taxonomy_settings ) ? wp_json_encode( $taxonomy_settings ) : (string) $taxonomy_settings;
    331     } else {
    332         error_log( '[TAXONOMY API DEBUG] taxonomy_settings unavailable; proceeding with availableTaxonomies only' );
    333     }
     301    } else {
     302    }
    334303    $payload['availableTaxonomies'] = $available_taxonomies;
    335304
     
    345314    );
    346315
    347     error_log( '[TAXONOMY API DEBUG] API response received' );
     316   
    348317
    349318    if ( is_wp_error( $api_response ) ) {
     
    362331    $taxonomy_data = json_decode( $response_body, true );
    363332
    364     error_log( '[TAXONOMY API DEBUG] Raw API response: ' . $response_body );
    365     error_log( '[TAXONOMY API DEBUG] Parsed taxonomy data: ' . print_r( $taxonomy_data, true ) );
     333   
    366334
    367335    $result_key = null;
     
    371339        $result_key = 'taxonomy';
    372340    }
    373     if ( ! $result_key ) {
    374         error_log( '[TAXONOMY API DEBUG] No taxonomy suggestions received from AI' );
     341    if ( ! $result_key ) {
    375342        return new WP_Error( 'no_taxonomy_suggestions', 'No taxonomy suggestions received from AI' );
    376343    }
    377344
    378     error_log( '[TAXONOMY API DEBUG] Applying taxonomy suggestions: ' . print_r( $taxonomy_data[ $result_key ], true ) );
     345   
    379346
    380347    $apply_result = saiap_apply_post_taxonomy( $post_id, $taxonomy_data[ $result_key ] );
  • clearpost-simple-ai-auto-post/trunk/readme.txt

    r3373470 r3387025  
    44Requires at least: 5.0
    55Tested up to: 6.8
    6 Stable tag: 1.1.8
     6Stable tag: 1.2.0
    77Requires PHP: 7.2
    88License: GPLv2 or later
  • clearpost-simple-ai-auto-post/trunk/simple-ai-auto-post.php

    r3373470 r3387025  
    33Plugin Name: ClearPost Simple AI Auto Post | Create Content with AI
    44Description: Your AI Agent for SEO, in WordPress. An AI content marketer that knows your site, then schedules and generates posts every day.
    5 Plugin URI: https://gopurposego.com/simple-ai-blog-post-generator-wordpress-plugin/
    6 Version: 1.1.8
     5Plugin URI: https://clearpostplugin.com/
     6Version: 1.2.0
    77License: GPLv2 or later
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1818
    1919// Define plugin version
    20 define( 'SAIAP_VERSION', '1.1.8' );
     20define( 'SAIAP_VERSION', '1.2.0' );
    2121
    2222// Optionally enable Demo Mode (for internal testing only)
    2323$__saiap_demo_enabled = ( defined( 'SAIAP_DEMO_MODE' ) && SAIAP_DEMO_MODE );
    24 if ( function_exists( 'error_log' ) ) {
    25     error_log( '[SAIAP Demo] Constant present=' . ( defined( 'SAIAP_DEMO_MODE' ) ? 'yes' : 'no' ) . ', enabled=' . ( $__saiap_demo_enabled ? 'true' : 'false' ) );
    26 }
     24 
    2725
    2826if ( $__saiap_demo_enabled ) {
    2927    $__saiap_demo_bootstrap = plugin_dir_path( __FILE__ ) . 'dev/demo-mode/bootstrap.php';
    30     if ( ! file_exists( $__saiap_demo_bootstrap ) ) {
    31         if ( function_exists( 'error_log' ) ) {
    32             error_log( '[SAIAP Demo] Enabled but bootstrap not found at: ' . $__saiap_demo_bootstrap );
    33         }
     28    if ( ! file_exists( $__saiap_demo_bootstrap ) ) {
    3429        add_action( 'admin_notices', function() use ($__saiap_demo_bootstrap) {
    3530            echo '<div class="notice notice-warning"><p><strong>Simple AI Auto Post:</strong> Demo Mode is enabled but demo files were not found at <code>' . esc_html( $__saiap_demo_bootstrap ) . '</code>. Make sure you installed the plugin from the Git repo (release ZIPs exclude dev/).</p></div>';
    3631        } );
    37     } else {
    38         if ( function_exists( 'error_log' ) ) {
    39             error_log( '[SAIAP Demo] Loading bootstrap: ' . $__saiap_demo_bootstrap );
    40         }
     32    } else {
    4133        // Optional: debug console log to confirm load path (kept silent in UI)
    4234        add_action( 'admin_footer', function() use ($__saiap_demo_bootstrap) {
     
    193185    add_option( 'saiap_context_post_age', '365' ); // days
    194186
     187    // Notifications defaults
     188    $admin_email_default = get_option( 'admin_email', '' );
     189    add_option( 'saiap_notify_on_schedule', 'no' );
     190    add_option( 'saiap_notify_recipients', $admin_email_default );
     191
    195192    register_setting(
    196193        'saiap_options_group',
     
    256253            'sanitize_callback' => 'absint',
    257254            'default'           => 365,
     255        )
     256    );
     257
     258    // Register notification settings
     259    register_setting(
     260        'saiap_options_group',
     261        'saiap_notify_on_schedule',
     262        array(
     263            'type'              => 'string',
     264            'sanitize_callback' => 'sanitize_text_field',
     265            'default'           => 'no',
     266        )
     267    );
     268    register_setting(
     269        'saiap_options_group',
     270        'saiap_notify_recipients',
     271        array(
     272            'type'              => 'string',
     273            'sanitize_callback' => 'sanitize_text_field',
     274            'default'           => $admin_email_default,
    258275        )
    259276    );
Note: See TracChangeset for help on using the changeset viewer.