Changeset 3387025
- Timestamp:
- 10/30/2025 11:29:07 AM (5 months ago)
- Location:
- clearpost-simple-ai-auto-post
- Files:
-
- 18 edited
- 26 copied
-
tags/1.2.0 (copied) (copied from clearpost-simple-ai-auto-post/trunk)
-
tags/1.2.0/assets (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets)
-
tags/1.2.0/assets/css/design-system.css (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets/css/design-system.css)
-
tags/1.2.0/assets/css/editor-chat.css (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets/css/editor-chat.css)
-
tags/1.2.0/assets/css/onboarding.css (modified) (5 diffs)
-
tags/1.2.0/assets/css/scheduler.css (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets/css/scheduler.css)
-
tags/1.2.0/assets/js/admin.js (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets/js/admin.js) (1 diff)
-
tags/1.2.0/assets/js/dist/editor-chat.bundle.js (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets/js/dist/editor-chat.bundle.js)
-
tags/1.2.0/assets/js/editor-chat.js (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets/js/editor-chat.js)
-
tags/1.2.0/assets/js/scheduler.js (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets/js/scheduler.js) (4 diffs)
-
tags/1.2.0/assets/js/site-context.js (copied) (copied from clearpost-simple-ai-auto-post/trunk/assets/js/site-context.js) (2 diffs)
-
tags/1.2.0/includes (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes)
-
tags/1.2.0/includes/ai-requests.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/ai-requests.php) (4 diffs)
-
tags/1.2.0/includes/ai-settings.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/ai-settings.php) (2 diffs)
-
tags/1.2.0/includes/editor-chat.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/editor-chat.php) (4 diffs)
-
tags/1.2.0/includes/generate.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/generate.php) (2 diffs)
-
tags/1.2.0/includes/images.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/images.php) (1 diff)
-
tags/1.2.0/includes/licensing.php (modified) (6 diffs)
-
tags/1.2.0/includes/onboarding.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/onboarding.php) (6 diffs)
-
tags/1.2.0/includes/scheduler.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/scheduler.php) (1 diff)
-
tags/1.2.0/includes/site-context.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/site-context.php) (4 diffs)
-
tags/1.2.0/includes/taxonomy.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/includes/taxonomy.php) (18 diffs)
-
tags/1.2.0/index.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/index.php)
-
tags/1.2.0/languages (copied) (copied from clearpost-simple-ai-auto-post/trunk/languages)
-
tags/1.2.0/license.txt (copied) (copied from clearpost-simple-ai-auto-post/trunk/license.txt)
-
tags/1.2.0/readme.txt (copied) (copied from clearpost-simple-ai-auto-post/trunk/readme.txt) (1 diff)
-
tags/1.2.0/simple-ai-auto-post.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/simple-ai-auto-post.php) (4 diffs)
-
tags/1.2.0/uninstall.php (copied) (copied from clearpost-simple-ai-auto-post/trunk/uninstall.php)
-
trunk/assets/css/onboarding.css (modified) (5 diffs)
-
trunk/assets/js/admin.js (modified) (1 diff)
-
trunk/assets/js/scheduler.js (modified) (4 diffs)
-
trunk/assets/js/site-context.js (modified) (2 diffs)
-
trunk/includes/ai-requests.php (modified) (4 diffs)
-
trunk/includes/ai-settings.php (modified) (2 diffs)
-
trunk/includes/editor-chat.php (modified) (4 diffs)
-
trunk/includes/generate.php (modified) (2 diffs)
-
trunk/includes/images.php (modified) (1 diff)
-
trunk/includes/licensing.php (modified) (6 diffs)
-
trunk/includes/onboarding.php (modified) (6 diffs)
-
trunk/includes/scheduler.php (modified) (1 diff)
-
trunk/includes/site-context.php (modified) (4 diffs)
-
trunk/includes/taxonomy.php (modified) (18 diffs)
-
trunk/readme.txt (modified) (1 diff)
-
trunk/simple-ai-auto-post.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
clearpost-simple-ai-auto-post/tags/1.2.0/assets/css/onboarding.css
r3357104 r3387025 29 29 30 30 .saiap-onboarding-container { 31 padding: 24px;31 padding: 18px; /* reduced ~25% */ 32 32 } 33 33 34 34 /* Header section */ 35 35 .saiap-onboarding-header { 36 margin-bottom: 20px;36 margin-bottom: 14px; /* reduced spacing */ 37 37 text-align: center; 38 38 } … … 58 58 align-items: center; 59 59 gap: 16px; 60 margin-bottom: 24px;61 padding: 0 20px;60 margin-bottom: 16px; /* reduced spacing */ 61 padding: 0 14px; /* reduced side padding */ 62 62 } 63 63 … … 109 109 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); 110 110 gap: 20px; 111 margin-bottom: 20px;111 margin-bottom: 14px; /* reduced spacing */ 112 112 } 113 113 … … 117 117 align-items: flex-start; 118 118 gap: 12px; 119 padding: 1 6px;119 padding: 12px; /* reduced ~25% */ 120 120 background: #ffffff; 121 121 border-radius: 8px; … … 225 225 .saiap-onboarding-help { 226 226 text-align: center; 227 padding-top: 1 6px;227 padding-top: 12px; /* reduced spacing */ 228 228 border-top: 1px solid #e5e7eb; 229 229 } 230 230 231 231 .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; 235 242 } 236 243 -
clearpost-simple-ai-auto-post/tags/1.2.0/assets/js/admin.js
r3373464 r3387025 283 283 } 284 284 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 285 344 // Tracking consent notice handlers 286 345 $('#saiap-tracking-allow').on('click', function() { -
clearpost-simple-ai-auto-post/tags/1.2.0/assets/js/scheduler.js
r3373464 r3387025 39 39 <div class="prompt-actions"> 40 40 <button class="button-secondary edit-prompt">${saiapAdmin.i18n.edit}</button> 41 <button class="button-secondary regenerate-prompt">${saiapAdmin.i18n.regenerate}</button>42 41 </div> 43 42 </div> … … 101 100 const noticeHtml = ` 102 101 <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> 106 105 <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> 109 107 <button class="button-secondary notice-dismiss-btn" style="margin-left: 10px;">Dismiss</button> 110 108 </p> … … 117 115 } 118 116 119 // Handle upgrade button click120 $('.premium-upgrade-btn').on('click', function(e) {121 e.preventDefault();122 window.open('https://clearpostplugin.com/#pricing', '_blank');123 });124 125 117 // Handle dismiss button 126 118 $('.notice-dismiss-btn').on('click', function(e) { … … 145 137 }); 146 138 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 189 140 190 141 // Modal handling -
clearpost-simple-ai-auto-post/tags/1.2.0/assets/js/site-context.js
r3373464 r3387025 195 195 const noticeHtml = ` 196 196 <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> 199 199 <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> 201 201 <button class="button-secondary notice-dismiss-btn" style="margin-left: 10px;">Dismiss</button> 202 202 </p> … … 212 212 $('.premium-upgrade-btn').on('click', function(e) { 213 213 e.preventDefault(); 214 window.open('https://clearpostplugin.com/ #pricing', '_blank');214 window.open('https://clearpostplugin.com/', '_blank'); 215 215 }); 216 216 -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/ai-requests.php
r3373464 r3387025 42 42 $messages[] = array( 'role' => 'user', 'content' => $user_message ); 43 43 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' ); 106 118 107 119 } elseif ( $provider === 'anthropic' ) { … … 141 153 } 142 154 143 // Prepare request body for Anthropic 155 // Prepare request body for Anthropic (enable web search + JSON tool) 144 156 $request_body = array( 145 157 'model' => $model ?: 'claude-sonnet-4-20250514', 146 158 '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 ), 149 161 ); 150 162 … … 188 200 if ( isset( $data['content'] ) && is_array( $data['content'] ) ) { 189 201 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 ) { 191 209 return wp_json_encode( $content_block['input'] ); 192 210 } … … 548 566 $content = $data['candidates'][0]['content']['parts'][0]['text']; 549 567 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 } 597 633 } else { 598 634 // Unknown provider -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/ai-settings.php
r3373464 r3387025 91 91 92 92 /** 93 * AJAX callback for saving notification settings 94 */ 95 function 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 114 add_action( 'wp_ajax_saiap_save_notifications', 'saiap_save_notifications_callback' ); 115 116 /** 93 117 * Render the Settings tab 94 118 */ 95 119 function saiap_render_ai_settings_tab( $models = array() ) { 120 $license_key = get_option( 'saiap_license_key', '' ); 96 121 ?> 97 122 <div class="tab-content ai-settings-tab-content"> 98 123 <div class="container"> 124 <?php if ( empty( $license_key ) ) : ?> 99 125 <div class="col col-12"> 100 126 <div class="card"> 101 127 <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> 104 130 <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> 106 132 </div> 107 133 … … 145 171 </div> 146 172 </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> 147 207 </div> 148 208 </div> -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/editor-chat.php
r3373464 r3387025 64 64 } 65 65 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 75 70 $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. 76 71 … … 120 115 " . $current_content; 121 116 122 // Prepare the user message117 // Prepare the user message 123 118 $user_message = "My request is: " . $message; 124 119 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 } 159 221 } 160 222 … … 176 238 177 239 $post_id = 0; 240 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only access to current editor screen context 178 241 if ( isset( $_GET['post'] ) ) { 179 242 $post_id = intval( $_GET['post'] ); … … 187 250 $post_type = $post->post_type; 188 251 } 252 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only post_type detection in editor context 189 253 } elseif ( isset( $_GET['post_type'] ) ) { 190 254 $post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) ); -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/generate.php
r3373464 r3387025 144 144 } 145 145 } 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 } 152 149 153 150 // Return success data … … 450 447 <div class="notice notice-info inline"> 451 448 <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> 454 451 </p> 455 452 <p style="margin: 8px 0;"> -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/images.php
r3373464 r3387025 169 169 170 170 $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', 172 172 'tmp_name' => $tmp, 173 173 ); 174 174 175 175 $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 } 181 181 182 182 $filename = $results['file']; -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/licensing.php
r3357104 r3387025 130 130 131 131 ?> 132 <?php if ( empty( $license_key ) ) : ?> 132 133 <div class="container"> 133 134 <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> 135 136 136 137 <div class="row"> … … 138 139 <p class="body-text spacing-16-row"> 139 140 <?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' ); 141 142 ?> 142 143 </p> 143 144 <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> 150 151 <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Premium support', 'clearpost-simple-ai-auto-post' ); ?></li> 151 152 </ul> … … 161 162 162 163 <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' ); ?> 164 165 </p> 165 166 … … 177 178 178 179 <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' ); ?> 181 182 </a> 182 183 </div> … … 186 187 </div> 187 188 </div> 189 <?php endif; ?> 188 190 <div class="container"> 189 191 <div class="card"> … … 234 236 <p class="small-text" style="margin: 0;"> 235 237 <?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> 237 239 </p> 238 240 </div> -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/onboarding.php
r3373464 r3387025 19 19 // phpcs:ignore WordPress.Security.NonceVerification.Recommended 20 20 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 } 22 26 } 23 27 … … 102 106 */ 103 107 function 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 104 114 // Don't show if dismissed or complete 105 115 if ( saiap_onboarding_is_dismissed() || saiap_onboarding_is_complete() ) { … … 130 140 <h3><?php esc_html_e( 'Get Started with ClearPost Simple AI Auto Post', 'clearpost-simple-ai-auto-post' ); ?></h3> 131 141 <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>139 142 </div> 140 143 … … 152 155 <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> 153 156 <?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> 160 158 <?php endif; ?> 161 159 </div> … … 198 196 199 197 <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> 202 200 </div> 203 201 </div> … … 239 237 // Only load on plugin settings page 240 238 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 ) ) { 241 245 return; 242 246 } -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/scheduler.php
r3373464 r3387025 510 510 if ( is_wp_error( $result ) ) { 511 511 $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 ); 512 598 } 513 599 514 600 } catch ( Exception $e ) { 515 601 $status_to_set = 'failed'; 602 do_action( 'saiap_scheduler_post_generation_failed', $prompt, new WP_Error( 'scheduler_exception', $e->getMessage() ) ); 516 603 } 517 604 -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/site-context.php
r3373464 r3387025 38 38 <div class="col col-12"> 39 39 <div class="card spacing-24"> 40 <h2 class="spacing-16-row"><?php esc_html_e( ' Site ContextAgent', '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> 41 41 42 42 <div class="spacing-16"> 43 43 <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' ); ?> 45 45 </p> 46 46 … … 352 352 $taxonomy_data = array(); 353 353 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 357 355 358 356 foreach ( $all_taxonomies as $tax_slug => $taxonomy ) { 359 error_log( '[TAXONOMY DEBUG] Processing taxonomy: ' . $tax_slug . ' (hierarchical: ' . ( $taxonomy->hierarchical ? 'true' : 'false' ) . ')' ); 357 360 358 361 359 $terms = wp_get_post_terms( $post->ID, $tax_slug, array( 'fields' => 'all' ) ); 362 360 363 error_log( '[TAXONOMY DEBUG] Raw terms for ' . $tax_slug . ': ' . print_r( $terms, true ) ); 361 364 362 365 363 if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) { … … 381 379 ); 382 380 383 error_log( '[TAXONOMY DEBUG] Raw term data for ' . $tax_slug . ': ' . print_r( $raw_term_data, true ) ); 381 384 382 } else { 385 error_log( '[TAXONOMY DEBUG] No terms found for ' . $tax_slug . ' or error occurred' ); 383 386 384 } 387 385 } 388 386 389 error_log( '[TAXONOMY DEBUG] Final taxonomy data for post ' . $post->ID . ': ' . print_r( $taxonomy_data, true ) ); 387 390 388 391 389 $formatted_posts[] = array( … … 491 489 ); 492 490 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 494 492 495 493 $response = wp_remote_post( -
clearpost-simple-ai-auto-post/tags/1.2.0/includes/taxonomy.php
r3373464 r3387025 27 27 */ 28 28 function 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 ) );31 29 32 30 if ( ! $post_id || ! is_numeric( $post_id ) ) { … … 44 42 if ( ! empty( $taxonomy_suggestions ) && is_array( $taxonomy_suggestions ) ) { 45 43 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 } 52 49 $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 59 55 60 56 $term_ids = array(); 61 57 62 58 // 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'] ) ) { 65 60 66 61 // First pass: Create all terms and build a map of suggested term_id to actual term_id … … 76 71 } 77 72 78 error_log( '[TAXONOMY APPLY DEBUG] Processing term: ' . $term_name . ' (suggested parent: ' . $suggested_parent . ')' ); 73 79 74 80 75 // Check if term exists by name … … 86 81 // For hierarchical taxonomies, we need to find the actual parent term_id 87 82 $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 } 92 86 } 93 87 94 88 $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 ) ) { 96 90 $actual_term_id = $term_result['term_id']; 97 91 $term_ids[] = $actual_term_id; … … 99 93 $suggested_term_id = isset( $term_data['term_id'] ) ? intval( $term_data['term_id'] ) : count( $term_id_map ) + 1; 100 94 $term_id_map[ $suggested_term_id ] = $actual_term_id; 101 error_log( '[TAXONOMY APPLY DEBUG] Created new term: ' . $term_name . ' (ID: ' . $actual_term_id . ')' );102 95 } else { 103 96 $errors[] = $term_result->get_error_message(); 104 error_log( '[TAXONOMY APPLY DEBUG] Error creating term ' . $term_name . ': ' . $term_result->get_error_message() );105 97 } 106 98 } else { … … 108 100 $actual_term_id = $existing_term->term_id; 109 101 $term_ids[] = $actual_term_id; 110 // Map the suggested term_id to the actual term_id102 // Map the suggested term_id to the actual term_id 111 103 $suggested_term_id = isset( $term_data['term_id'] ) ? intval( $term_data['term_id'] ) : count( $term_id_map ) + 1; 112 104 $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 114 106 } 115 107 } … … 127 119 128 120 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 } 135 125 } 136 126 } … … 139 129 } 140 130 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 149 138 150 139 $success_count = 0; 151 140 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 );153 141 $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 ) ) { 155 143 $success_count += count( $term_ids_to_apply ); 156 error_log( '[TAXONOMY APPLY DEBUG] Successfully applied terms to ' . $tax_slug );157 144 } else { 158 145 $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 164 150 165 151 if ( ! empty( $errors ) ) { … … 184 170 */ 185 171 function 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 ) );188 172 189 173 foreach ( $terms as $key => $value ) { … … 191 175 $children = is_string( $key ) ? $value : array(); 192 176 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 195 178 196 179 if ( is_array( $term_name ) ) { 197 180 // Handle cases where the array is not associative 198 error_log( '[TAXONOMY NESTED DEBUG] Term name is array, processing sub-items' ); 181 199 182 foreach ( $term_name as $sub_key => $sub_value ) { 200 183 saiap_create_nested_terms( array( $sub_key => $sub_value ), $tax_slug, $parent_id, $term_ids, $errors ); … … 204 187 205 188 $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 212 193 $term = get_term_by( 'name', $term_name, $tax_slug ); 213 194 if ( ! $term ) { 214 error_log( '[TAXONOMY NESTED DEBUG] Term not found, creating new term: ' . $term_name . ' with parent_id: ' . $parent_id );215 195 $term_result = wp_insert_term( $term_name, $tax_slug, array( 'parent' => $parent_id ) ); 216 196 if ( ! is_wp_error( $term_result ) ) { 217 197 $current_term_id = $term_result['term_id']; 218 error_log( '[TAXONOMY NESTED DEBUG] Successfully created term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );219 198 } else { 220 199 $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() );222 200 continue; 223 201 } 224 202 } else { 225 203 $current_term_id = $term['term_id']; 226 error_log( '[TAXONOMY NESTED DEBUG] Found existing term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );227 204 } 228 205 229 206 $term_ids[] = $current_term_id; 230 207 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 ) ) { 233 209 saiap_create_nested_terms( $children, $tax_slug, $current_term_id, $term_ids, $errors ); 234 210 } 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 240 215 } 241 216 … … 279 254 } 280 255 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 284 257 285 258 // Attempt to get taxonomy_settings from cached context or fetch fresh … … 290 263 } 291 264 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 ) ) { 297 268 $context_response = wp_remote_post( 298 269 'https://saiap.gopurposego.com/api/context', … … 316 287 $context_data = json_decode( $context_body, true ); 317 288 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']; 320 290 } 321 291 } … … 329 299 if ( ! empty( $taxonomy_settings ) ) { 330 300 $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 } 334 303 $payload['availableTaxonomies'] = $available_taxonomies; 335 304 … … 345 314 ); 346 315 347 error_log( '[TAXONOMY API DEBUG] API response received' ); 316 348 317 349 318 if ( is_wp_error( $api_response ) ) { … … 362 331 $taxonomy_data = json_decode( $response_body, true ); 363 332 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 366 334 367 335 $result_key = null; … … 371 339 $result_key = 'taxonomy'; 372 340 } 373 if ( ! $result_key ) { 374 error_log( '[TAXONOMY API DEBUG] No taxonomy suggestions received from AI' ); 341 if ( ! $result_key ) { 375 342 return new WP_Error( 'no_taxonomy_suggestions', 'No taxonomy suggestions received from AI' ); 376 343 } 377 344 378 error_log( '[TAXONOMY API DEBUG] Applying taxonomy suggestions: ' . print_r( $taxonomy_data[ $result_key ], true ) ); 345 379 346 380 347 $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 4 4 Requires at least: 5.0 5 5 Tested up to: 6.8 6 Stable tag: 1. 1.86 Stable tag: 1.2.0 7 7 Requires PHP: 7.2 8 8 License: GPLv2 or later -
clearpost-simple-ai-auto-post/tags/1.2.0/simple-ai-auto-post.php
r3373470 r3387025 3 3 Plugin Name: ClearPost Simple AI Auto Post | Create Content with AI 4 4 Description: 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.85 Plugin URI: https://clearpostplugin.com/ 6 Version: 1.2.0 7 7 License: GPLv2 or later 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 18 18 19 19 // Define plugin version 20 define( 'SAIAP_VERSION', '1. 1.8' );20 define( 'SAIAP_VERSION', '1.2.0' ); 21 21 22 22 // Optionally enable Demo Mode (for internal testing only) 23 23 $__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 27 25 28 26 if ( $__saiap_demo_enabled ) { 29 27 $__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 ) ) { 34 29 add_action( 'admin_notices', function() use ($__saiap_demo_bootstrap) { 35 30 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>'; 36 31 } ); 37 } else { 38 if ( function_exists( 'error_log' ) ) { 39 error_log( '[SAIAP Demo] Loading bootstrap: ' . $__saiap_demo_bootstrap ); 40 } 32 } else { 41 33 // Optional: debug console log to confirm load path (kept silent in UI) 42 34 add_action( 'admin_footer', function() use ($__saiap_demo_bootstrap) { … … 193 185 add_option( 'saiap_context_post_age', '365' ); // days 194 186 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 195 192 register_setting( 196 193 'saiap_options_group', … … 256 253 'sanitize_callback' => 'absint', 257 254 '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, 258 275 ) 259 276 ); -
clearpost-simple-ai-auto-post/trunk/assets/css/onboarding.css
r3357104 r3387025 29 29 30 30 .saiap-onboarding-container { 31 padding: 24px;31 padding: 18px; /* reduced ~25% */ 32 32 } 33 33 34 34 /* Header section */ 35 35 .saiap-onboarding-header { 36 margin-bottom: 20px;36 margin-bottom: 14px; /* reduced spacing */ 37 37 text-align: center; 38 38 } … … 58 58 align-items: center; 59 59 gap: 16px; 60 margin-bottom: 24px;61 padding: 0 20px;60 margin-bottom: 16px; /* reduced spacing */ 61 padding: 0 14px; /* reduced side padding */ 62 62 } 63 63 … … 109 109 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); 110 110 gap: 20px; 111 margin-bottom: 20px;111 margin-bottom: 14px; /* reduced spacing */ 112 112 } 113 113 … … 117 117 align-items: flex-start; 118 118 gap: 12px; 119 padding: 1 6px;119 padding: 12px; /* reduced ~25% */ 120 120 background: #ffffff; 121 121 border-radius: 8px; … … 225 225 .saiap-onboarding-help { 226 226 text-align: center; 227 padding-top: 1 6px;227 padding-top: 12px; /* reduced spacing */ 228 228 border-top: 1px solid #e5e7eb; 229 229 } 230 230 231 231 .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; 235 242 } 236 243 -
clearpost-simple-ai-auto-post/trunk/assets/js/admin.js
r3373464 r3387025 283 283 } 284 284 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 285 344 // Tracking consent notice handlers 286 345 $('#saiap-tracking-allow').on('click', function() { -
clearpost-simple-ai-auto-post/trunk/assets/js/scheduler.js
r3373464 r3387025 39 39 <div class="prompt-actions"> 40 40 <button class="button-secondary edit-prompt">${saiapAdmin.i18n.edit}</button> 41 <button class="button-secondary regenerate-prompt">${saiapAdmin.i18n.regenerate}</button>42 41 </div> 43 42 </div> … … 101 100 const noticeHtml = ` 102 101 <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> 106 105 <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> 109 107 <button class="button-secondary notice-dismiss-btn" style="margin-left: 10px;">Dismiss</button> 110 108 </p> … … 117 115 } 118 116 119 // Handle upgrade button click120 $('.premium-upgrade-btn').on('click', function(e) {121 e.preventDefault();122 window.open('https://clearpostplugin.com/#pricing', '_blank');123 });124 125 117 // Handle dismiss button 126 118 $('.notice-dismiss-btn').on('click', function(e) { … … 145 137 }); 146 138 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 189 140 190 141 // Modal handling -
clearpost-simple-ai-auto-post/trunk/assets/js/site-context.js
r3373464 r3387025 195 195 const noticeHtml = ` 196 196 <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> 199 199 <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> 201 201 <button class="button-secondary notice-dismiss-btn" style="margin-left: 10px;">Dismiss</button> 202 202 </p> … … 212 212 $('.premium-upgrade-btn').on('click', function(e) { 213 213 e.preventDefault(); 214 window.open('https://clearpostplugin.com/ #pricing', '_blank');214 window.open('https://clearpostplugin.com/', '_blank'); 215 215 }); 216 216 -
clearpost-simple-ai-auto-post/trunk/includes/ai-requests.php
r3373464 r3387025 42 42 $messages[] = array( 'role' => 'user', 'content' => $user_message ); 43 43 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' ); 106 118 107 119 } elseif ( $provider === 'anthropic' ) { … … 141 153 } 142 154 143 // Prepare request body for Anthropic 155 // Prepare request body for Anthropic (enable web search + JSON tool) 144 156 $request_body = array( 145 157 'model' => $model ?: 'claude-sonnet-4-20250514', 146 158 '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 ), 149 161 ); 150 162 … … 188 200 if ( isset( $data['content'] ) && is_array( $data['content'] ) ) { 189 201 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 ) { 191 209 return wp_json_encode( $content_block['input'] ); 192 210 } … … 548 566 $content = $data['candidates'][0]['content']['parts'][0]['text']; 549 567 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 } 597 633 } else { 598 634 // Unknown provider -
clearpost-simple-ai-auto-post/trunk/includes/ai-settings.php
r3373464 r3387025 91 91 92 92 /** 93 * AJAX callback for saving notification settings 94 */ 95 function 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 114 add_action( 'wp_ajax_saiap_save_notifications', 'saiap_save_notifications_callback' ); 115 116 /** 93 117 * Render the Settings tab 94 118 */ 95 119 function saiap_render_ai_settings_tab( $models = array() ) { 120 $license_key = get_option( 'saiap_license_key', '' ); 96 121 ?> 97 122 <div class="tab-content ai-settings-tab-content"> 98 123 <div class="container"> 124 <?php if ( empty( $license_key ) ) : ?> 99 125 <div class="col col-12"> 100 126 <div class="card"> 101 127 <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> 104 130 <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> 106 132 </div> 107 133 … … 145 171 </div> 146 172 </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> 147 207 </div> 148 208 </div> -
clearpost-simple-ai-auto-post/trunk/includes/editor-chat.php
r3373464 r3387025 64 64 } 65 65 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 75 70 $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. 76 71 … … 120 115 " . $current_content; 121 116 122 // Prepare the user message117 // Prepare the user message 123 118 $user_message = "My request is: " . $message; 124 119 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 } 159 221 } 160 222 … … 176 238 177 239 $post_id = 0; 240 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only access to current editor screen context 178 241 if ( isset( $_GET['post'] ) ) { 179 242 $post_id = intval( $_GET['post'] ); … … 187 250 $post_type = $post->post_type; 188 251 } 252 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only post_type detection in editor context 189 253 } elseif ( isset( $_GET['post_type'] ) ) { 190 254 $post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) ); -
clearpost-simple-ai-auto-post/trunk/includes/generate.php
r3373464 r3387025 144 144 } 145 145 } 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 } 152 149 153 150 // Return success data … … 450 447 <div class="notice notice-info inline"> 451 448 <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> 454 451 </p> 455 452 <p style="margin: 8px 0;"> -
clearpost-simple-ai-auto-post/trunk/includes/images.php
r3373464 r3387025 169 169 170 170 $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', 172 172 'tmp_name' => $tmp, 173 173 ); 174 174 175 175 $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 } 181 181 182 182 $filename = $results['file']; -
clearpost-simple-ai-auto-post/trunk/includes/licensing.php
r3357104 r3387025 130 130 131 131 ?> 132 <?php if ( empty( $license_key ) ) : ?> 132 133 <div class="container"> 133 134 <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> 135 136 136 137 <div class="row"> … … 138 139 <p class="body-text spacing-16-row"> 139 140 <?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' ); 141 142 ?> 142 143 </p> 143 144 <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> 150 151 <li class="body-text spacing-8-row"><?php esc_html_e( '✓ Premium support', 'clearpost-simple-ai-auto-post' ); ?></li> 151 152 </ul> … … 161 162 162 163 <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' ); ?> 164 165 </p> 165 166 … … 177 178 178 179 <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' ); ?> 181 182 </a> 182 183 </div> … … 186 187 </div> 187 188 </div> 189 <?php endif; ?> 188 190 <div class="container"> 189 191 <div class="card"> … … 234 236 <p class="small-text" style="margin: 0;"> 235 237 <?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> 237 239 </p> 238 240 </div> -
clearpost-simple-ai-auto-post/trunk/includes/onboarding.php
r3373464 r3387025 19 19 // phpcs:ignore WordPress.Security.NonceVerification.Recommended 20 20 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 } 22 26 } 23 27 … … 102 106 */ 103 107 function 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 104 114 // Don't show if dismissed or complete 105 115 if ( saiap_onboarding_is_dismissed() || saiap_onboarding_is_complete() ) { … … 130 140 <h3><?php esc_html_e( 'Get Started with ClearPost Simple AI Auto Post', 'clearpost-simple-ai-auto-post' ); ?></h3> 131 141 <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>139 142 </div> 140 143 … … 152 155 <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> 153 156 <?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> 160 158 <?php endif; ?> 161 159 </div> … … 198 196 199 197 <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> 202 200 </div> 203 201 </div> … … 239 237 // Only load on plugin settings page 240 238 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 ) ) { 241 245 return; 242 246 } -
clearpost-simple-ai-auto-post/trunk/includes/scheduler.php
r3373464 r3387025 510 510 if ( is_wp_error( $result ) ) { 511 511 $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 ); 512 598 } 513 599 514 600 } catch ( Exception $e ) { 515 601 $status_to_set = 'failed'; 602 do_action( 'saiap_scheduler_post_generation_failed', $prompt, new WP_Error( 'scheduler_exception', $e->getMessage() ) ); 516 603 } 517 604 -
clearpost-simple-ai-auto-post/trunk/includes/site-context.php
r3373464 r3387025 38 38 <div class="col col-12"> 39 39 <div class="card spacing-24"> 40 <h2 class="spacing-16-row"><?php esc_html_e( ' Site ContextAgent', '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> 41 41 42 42 <div class="spacing-16"> 43 43 <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' ); ?> 45 45 </p> 46 46 … … 352 352 $taxonomy_data = array(); 353 353 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 357 355 358 356 foreach ( $all_taxonomies as $tax_slug => $taxonomy ) { 359 error_log( '[TAXONOMY DEBUG] Processing taxonomy: ' . $tax_slug . ' (hierarchical: ' . ( $taxonomy->hierarchical ? 'true' : 'false' ) . ')' ); 357 360 358 361 359 $terms = wp_get_post_terms( $post->ID, $tax_slug, array( 'fields' => 'all' ) ); 362 360 363 error_log( '[TAXONOMY DEBUG] Raw terms for ' . $tax_slug . ': ' . print_r( $terms, true ) ); 361 364 362 365 363 if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) { … … 381 379 ); 382 380 383 error_log( '[TAXONOMY DEBUG] Raw term data for ' . $tax_slug . ': ' . print_r( $raw_term_data, true ) ); 381 384 382 } else { 385 error_log( '[TAXONOMY DEBUG] No terms found for ' . $tax_slug . ' or error occurred' ); 383 386 384 } 387 385 } 388 386 389 error_log( '[TAXONOMY DEBUG] Final taxonomy data for post ' . $post->ID . ': ' . print_r( $taxonomy_data, true ) ); 387 390 388 391 389 $formatted_posts[] = array( … … 491 489 ); 492 490 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 494 492 495 493 $response = wp_remote_post( -
clearpost-simple-ai-auto-post/trunk/includes/taxonomy.php
r3373464 r3387025 27 27 */ 28 28 function 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 ) );31 29 32 30 if ( ! $post_id || ! is_numeric( $post_id ) ) { … … 44 42 if ( ! empty( $taxonomy_suggestions ) && is_array( $taxonomy_suggestions ) ) { 45 43 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 } 52 49 $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 59 55 60 56 $term_ids = array(); 61 57 62 58 // 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'] ) ) { 65 60 66 61 // First pass: Create all terms and build a map of suggested term_id to actual term_id … … 76 71 } 77 72 78 error_log( '[TAXONOMY APPLY DEBUG] Processing term: ' . $term_name . ' (suggested parent: ' . $suggested_parent . ')' ); 73 79 74 80 75 // Check if term exists by name … … 86 81 // For hierarchical taxonomies, we need to find the actual parent term_id 87 82 $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 } 92 86 } 93 87 94 88 $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 ) ) { 96 90 $actual_term_id = $term_result['term_id']; 97 91 $term_ids[] = $actual_term_id; … … 99 93 $suggested_term_id = isset( $term_data['term_id'] ) ? intval( $term_data['term_id'] ) : count( $term_id_map ) + 1; 100 94 $term_id_map[ $suggested_term_id ] = $actual_term_id; 101 error_log( '[TAXONOMY APPLY DEBUG] Created new term: ' . $term_name . ' (ID: ' . $actual_term_id . ')' );102 95 } else { 103 96 $errors[] = $term_result->get_error_message(); 104 error_log( '[TAXONOMY APPLY DEBUG] Error creating term ' . $term_name . ': ' . $term_result->get_error_message() );105 97 } 106 98 } else { … … 108 100 $actual_term_id = $existing_term->term_id; 109 101 $term_ids[] = $actual_term_id; 110 // Map the suggested term_id to the actual term_id102 // Map the suggested term_id to the actual term_id 111 103 $suggested_term_id = isset( $term_data['term_id'] ) ? intval( $term_data['term_id'] ) : count( $term_id_map ) + 1; 112 104 $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 114 106 } 115 107 } … … 127 119 128 120 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 } 135 125 } 136 126 } … … 139 129 } 140 130 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 149 138 150 139 $success_count = 0; 151 140 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 );153 141 $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 ) ) { 155 143 $success_count += count( $term_ids_to_apply ); 156 error_log( '[TAXONOMY APPLY DEBUG] Successfully applied terms to ' . $tax_slug );157 144 } else { 158 145 $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 164 150 165 151 if ( ! empty( $errors ) ) { … … 184 170 */ 185 171 function 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 ) );188 172 189 173 foreach ( $terms as $key => $value ) { … … 191 175 $children = is_string( $key ) ? $value : array(); 192 176 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 195 178 196 179 if ( is_array( $term_name ) ) { 197 180 // Handle cases where the array is not associative 198 error_log( '[TAXONOMY NESTED DEBUG] Term name is array, processing sub-items' ); 181 199 182 foreach ( $term_name as $sub_key => $sub_value ) { 200 183 saiap_create_nested_terms( array( $sub_key => $sub_value ), $tax_slug, $parent_id, $term_ids, $errors ); … … 204 187 205 188 $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 212 193 $term = get_term_by( 'name', $term_name, $tax_slug ); 213 194 if ( ! $term ) { 214 error_log( '[TAXONOMY NESTED DEBUG] Term not found, creating new term: ' . $term_name . ' with parent_id: ' . $parent_id );215 195 $term_result = wp_insert_term( $term_name, $tax_slug, array( 'parent' => $parent_id ) ); 216 196 if ( ! is_wp_error( $term_result ) ) { 217 197 $current_term_id = $term_result['term_id']; 218 error_log( '[TAXONOMY NESTED DEBUG] Successfully created term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );219 198 } else { 220 199 $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() );222 200 continue; 223 201 } 224 202 } else { 225 203 $current_term_id = $term['term_id']; 226 error_log( '[TAXONOMY NESTED DEBUG] Found existing term: ' . $term_name . ' (ID: ' . $current_term_id . ')' );227 204 } 228 205 229 206 $term_ids[] = $current_term_id; 230 207 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 ) ) { 233 209 saiap_create_nested_terms( $children, $tax_slug, $current_term_id, $term_ids, $errors ); 234 210 } 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 240 215 } 241 216 … … 279 254 } 280 255 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 284 257 285 258 // Attempt to get taxonomy_settings from cached context or fetch fresh … … 290 263 } 291 264 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 ) ) { 297 268 $context_response = wp_remote_post( 298 269 'https://saiap.gopurposego.com/api/context', … … 316 287 $context_data = json_decode( $context_body, true ); 317 288 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']; 320 290 } 321 291 } … … 329 299 if ( ! empty( $taxonomy_settings ) ) { 330 300 $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 } 334 303 $payload['availableTaxonomies'] = $available_taxonomies; 335 304 … … 345 314 ); 346 315 347 error_log( '[TAXONOMY API DEBUG] API response received' ); 316 348 317 349 318 if ( is_wp_error( $api_response ) ) { … … 362 331 $taxonomy_data = json_decode( $response_body, true ); 363 332 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 366 334 367 335 $result_key = null; … … 371 339 $result_key = 'taxonomy'; 372 340 } 373 if ( ! $result_key ) { 374 error_log( '[TAXONOMY API DEBUG] No taxonomy suggestions received from AI' ); 341 if ( ! $result_key ) { 375 342 return new WP_Error( 'no_taxonomy_suggestions', 'No taxonomy suggestions received from AI' ); 376 343 } 377 344 378 error_log( '[TAXONOMY API DEBUG] Applying taxonomy suggestions: ' . print_r( $taxonomy_data[ $result_key ], true ) ); 345 379 346 380 347 $apply_result = saiap_apply_post_taxonomy( $post_id, $taxonomy_data[ $result_key ] ); -
clearpost-simple-ai-auto-post/trunk/readme.txt
r3373470 r3387025 4 4 Requires at least: 5.0 5 5 Tested up to: 6.8 6 Stable tag: 1. 1.86 Stable tag: 1.2.0 7 7 Requires PHP: 7.2 8 8 License: GPLv2 or later -
clearpost-simple-ai-auto-post/trunk/simple-ai-auto-post.php
r3373470 r3387025 3 3 Plugin Name: ClearPost Simple AI Auto Post | Create Content with AI 4 4 Description: 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.85 Plugin URI: https://clearpostplugin.com/ 6 Version: 1.2.0 7 7 License: GPLv2 or later 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 18 18 19 19 // Define plugin version 20 define( 'SAIAP_VERSION', '1. 1.8' );20 define( 'SAIAP_VERSION', '1.2.0' ); 21 21 22 22 // Optionally enable Demo Mode (for internal testing only) 23 23 $__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 27 25 28 26 if ( $__saiap_demo_enabled ) { 29 27 $__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 ) ) { 34 29 add_action( 'admin_notices', function() use ($__saiap_demo_bootstrap) { 35 30 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>'; 36 31 } ); 37 } else { 38 if ( function_exists( 'error_log' ) ) { 39 error_log( '[SAIAP Demo] Loading bootstrap: ' . $__saiap_demo_bootstrap ); 40 } 32 } else { 41 33 // Optional: debug console log to confirm load path (kept silent in UI) 42 34 add_action( 'admin_footer', function() use ($__saiap_demo_bootstrap) { … … 193 185 add_option( 'saiap_context_post_age', '365' ); // days 194 186 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 195 192 register_setting( 196 193 'saiap_options_group', … … 256 253 'sanitize_callback' => 'absint', 257 254 '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, 258 275 ) 259 276 );
Note: See TracChangeset
for help on using the changeset viewer.