PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.8.1
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.8.1
8.8.2 8.8.1 8.8.0 8.7.9 8.7.8 8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 All 538 releases
← All changes | addons/automator/includes/actions/wordpress-actions.php +866 -866 8.7.9 → 8.8.1 View file →
@@ -1,866 +1,866 @@
1 -<?php
2 -/**
3 - * WordPress Actions
4 - *
5 - * @package WPbot_Automator
6 - */
7 -
8 -namespace WPbot_Automator\Actions;
9 -
10 -if ( ! defined( 'ABSPATH' ) ) {
11 - exit;
12 -}
13 -
14 -/**
15 - * Class WordPress_Actions
16 - */
17 -class WordPress_Actions extends Action {
18 -
19 - /**
20 - * Constructor
21 - */
22 - public function __construct() {
23 - $this->id = 'wordpress';
24 - $this->group = __( 'WordPress', 'wpbot-automator' );
25 - }
26 -
27 - /**
28 - * Execute the action
29 - *
30 - * @param array $action_data Configuration for this action from the workflow.
31 - * @param array $trigger_data Data passed from the trigger.
32 - * @return bool|array
33 - */
34 - public function execute( $action_data, $trigger_data ) {
35 - $action_id = isset( $action_data['actionId'] ) ? $action_data['actionId'] : '';
36 -
37 - if ( empty( $action_id ) ) {
38 - return false;
39 - }
40 -
41 - switch ( $action_id ) {
42 - case 'wp_create_user':
43 - case 'wp_user_register':
44 - return $this->create_user( $action_data, $trigger_data );
45 - case 'wp_update_user':
46 - return $this->update_user( $action_data, $trigger_data );
47 - case 'wp_delete_user':
48 - return $this->delete_user( $action_data, $trigger_data );
49 - case 'wp_create_post':
50 - case 'wp_create_page':
51 - return $this->create_post( $action_data, $trigger_data );
52 - case 'wp_update_post':
53 - return $this->update_post( $action_data, $trigger_data );
54 - case 'wp_create_comment':
55 - return $this->create_comment( $action_data, $trigger_data );
56 - case 'wp_reply_comment':
57 - return $this->reply_comment( $action_data, $trigger_data );
58 - case 'wp_ai_generated_post':
59 - return $this->ai_generated_post( $action_data, $trigger_data );
60 - case 'wp_ai_reply_comment':
61 - return $this->ai_reply_comment( $action_data, $trigger_data );
62 - case 'wp_ai_generate_social_posts':
63 - case 'wp_ai_generate_social_posts_pro':
64 - return $this->ai_generate_social_posts( $action_data, $trigger_data );
65 - // Add more cases here as needed...
66 - default:
67 - // Fallback for actions not yet fully implemented
68 - return array( 'success' => true, 'message' => "Action {$action_id} executed (simulated)" );
69 - }
70 - }
71 -
72 - /**
73 - * Reply to a comment
74 - */
75 - private function reply_comment( $config, $trigger_data ) {
76 - $config_values = isset( $config['config'] ) ? $config['config'] : array();
77 - $parent_id = isset( $config_values['parent_id'] ) ? $this->parse_tokens( $config_values['parent_id'], $trigger_data ) : 0;
78 - $comment_content = isset( $config_values['comment_content'] ) ? $this->parse_tokens( $config_values['comment_content'], $trigger_data ) : '';
79 -
80 - // Fallback for parent_id: use comment_id from trigger if available
81 - if ( empty( $parent_id ) && isset( $trigger_data['comment_id'] ) ) {
82 - $parent_id = is_scalar( $trigger_data['comment_id'] ) ? $trigger_data['comment_id'] : 0;
83 - //error_log( "WPbot Automator - reply_comment: Used fallback parent_id: " . $parent_id );
84 - }
85 -
86 - // Fallback for content: use 'response' or 'content' from trigger (AI output)
87 - if ( empty( $comment_content ) ) {
88 - if ( isset( $trigger_data['response'] ) ) {
89 - $comment_content = is_scalar( $trigger_data['response'] ) ? (string) $trigger_data['response'] : wp_json_encode( $trigger_data['response'] );
90 - //error_log( "WPbot Automator - reply_comment: Used fallback content from {response}" );
91 - } elseif ( isset( $trigger_data['content'] ) ) {
92 - $comment_content = is_scalar( $trigger_data['content'] ) ? (string) $trigger_data['content'] : wp_json_encode( $trigger_data['content'] );
93 - //error_log( "WPbot Automator - reply_comment: Used fallback content from {content}" );
94 - }
95 - }
96 -
97 - if ( empty( $parent_id ) ) {
98 - return array( 'success' => false, 'message' => 'Parent Comment ID is required.' );
99 - }
100 -
101 - if ( empty( $comment_content ) ) {
102 - return array( 'success' => false, 'message' => 'Reply Content is required.' );
103 - }
104 -
105 - $parent_comment = get_comment( $parent_id );
106 - if ( ! $parent_comment ) {
107 - return array(
108 - 'success' => false,
109 - 'message' => 'Parent comment not found for ID: ' . $parent_id,
110 - );
111 - }
112 -
113 - $comment_id = wp_insert_comment( array(
114 - 'comment_post_ID' => $parent_comment->comment_post_ID,
115 - 'comment_parent' => absint( $parent_id ),
116 - 'comment_content' => $comment_content,
117 - 'comment_type' => 'comment',
118 - 'comment_approved' => 1,
119 - ) );
120 -
121 - if ( ! $comment_id ) {
122 - return array(
123 - 'success' => false,
124 - 'message' => 'Failed to create reply.',
125 - );
126 - }
127 -
128 - return array(
129 - 'success' => true,
130 - 'comment_id' => $comment_id,
131 - 'message' => 'Reply created successfully.',
132 - );
133 - }
134 -
135 - /**
136 - * Create a new user
137 - */
138 - private function create_user( $config, $trigger_data ) {
139 - $config_values = isset( $config['config'] ) ? $config['config'] : array();
140 - $user_login = isset( $config_values['user_login'] ) ? $this->parse_tokens( $config_values['user_login'], $trigger_data ) : '';
141 - $user_email = isset( $config_values['user_email'] ) ? $this->parse_tokens( $config_values['user_email'], $trigger_data ) : '';
142 -
143 - // Smart Mapping Fallback: If not provided, try to find in trigger_data.
144 - if ( empty( $user_email ) ) {
145 - $user_email = $this->find_email_in_data( $trigger_data );
146 - }
147 -
148 - if ( empty( $user_login ) ) {
149 - // Try to find a name or use first part of email.
150 - $name = $this->find_name_in_data( $trigger_data );
151 - $user_login = ! empty( $name ) ? sanitize_user( strtolower( str_replace( ' ', '.', $name ) ) ) : ( ! empty( $user_email ) ? explode( '@', $user_email )[0] : '' );
152 -
153 - // Safety: WordPress usernames have a 60 character limit.
154 - if ( strlen( $user_login ) > 60 ) {
155 - $user_login = substr( $user_login, 0, 60 );
156 - }
157 - }
158 -
159 - $user_pass = isset( $config_values['user_pass'] ) ? $this->parse_tokens( $config_values['user_pass'], $trigger_data ) : wp_generate_password();
160 - $role = isset( $config_values['role'] ) ? $config_values['role'] : 'subscriber';
161 -
162 - if ( empty( $user_login ) || empty( $user_email ) ) {
163 - return array(
164 - 'success' => false,
165 - 'message' => sprintf( 'User login and email are required. Found Login: "%s", Email: "%s"', $user_login, $user_email ),
166 - );
167 - }
168 -
169 - if ( username_exists( $user_login ) ) {
170 - return array(
171 - 'success' => false,
172 - 'message' => "Username already exists: $user_login",
173 - );
174 - }
175 -
176 - if ( email_exists( $user_email ) ) {
177 - return array(
178 - 'success' => false,
179 - 'message' => "Email already exists: $user_email",
180 - );
181 - }
182 -
183 - $user_id = wp_insert_user( array(
184 - 'user_login' => $user_login,
185 - 'user_email' => $user_email,
186 - 'user_pass' => $user_pass,
187 - 'role' => $role,
188 - ) );
189 -
190 - if ( is_wp_error( $user_id ) ) {
191 - return array(
192 - 'success' => false,
193 - 'message' => $user_id->get_error_message(),
194 - );
195 - }
196 -
197 - return array(
198 - 'success' => true,
199 - 'user_id' => $user_id,
200 - 'message' => 'User created successfully.',
201 - );
202 - }
203 -
204 - /**
205 - * Helper: Find email in trigger data
206 - */
207 - private function find_email_in_data( $data ) {
208 - if ( ! is_array( $data ) ) return '';
209 -
210 - // Flatten and look for keys containing 'email'.
211 - $flattened = $this->flatten_array_recursive( $data );
212 - foreach ( $flattened as $key => $val ) {
213 - if ( is_string( $val ) && is_email( $val ) ) {
214 - return $val;
215 - }
216 - }
217 - return '';
218 - }
219 -
220 - /**
221 - * Helper: Find name in trigger data
222 - */
223 - private function find_name_in_data( $data ) {
224 - if ( ! is_array( $data ) ) return '';
225 -
226 - $flattened = $this->flatten_array_recursive( $data );
227 -
228 - $firstName = '';
229 - $lastName = '';
230 - $fullName = '';
231 -
232 - foreach ( $flattened as $key => $val ) {
233 - if ( ! is_string( $val ) || empty( $val ) ) continue;
234 - $low_key = strtolower( $key );
235 -
236 - // Priority 1: Explicit sub-fields (common in WPForms, etc).
237 - if ( strpos( $low_key, '.first' ) !== false || strpos( $low_key, 'first_name' ) !== false ) {
238 - $firstName = $val;
239 - }
240 - if ( strpos( $low_key, '.last' ) !== false || strpos( $low_key, 'last_name' ) !== false ) {
241 - $lastName = $val;
242 - }
243 -
244 - // Priority 2: Generic name field.
245 - if ( $low_key === 'fullname' || $low_key === 'full_name' || ( strpos( $low_key, 'name' ) !== false && strpos( $low_key, '.value' ) !== false ) ) {
246 - $fullName = $val;
247 - }
248 - }
249 -
250 - if ( ! empty( $firstName ) || ! empty( $lastName ) ) {
251 - return trim( $firstName . ' ' . $lastName );
252 - }
253 -
254 - if ( ! empty( $fullName ) ) {
255 - return $fullName;
256 - }
257 -
258 - // Fallback: Just the FIRST thing that looks like a name.
259 - foreach ( $flattened as $key => $val ) {
260 - $low_key = strtolower( $key );
261 - if ( strpos( $low_key, 'name' ) !== false ) {
262 - // Avoid labels.
263 - if ( is_string( $val ) && strlen( $val ) > 2 && strlen( $val ) < 50 ) {
264 - $low_val = strtolower( trim( $val ) );
265 - if ( ! in_array( $low_val, array( 'name', 'first name', 'last name', 'full name', 'email', 'message', 'subject' ) ) ) {
266 - return $val;
267 - }
268 - }
269 - }
270 - }
271 -
272 - return '';
273 - }
274 -
275 - /**
276 - * Helper to flatten array recursively
277 - */
278 - private function flatten_array_recursive( $array, $prefix = '' ) {
279 - $result = array();
280 - foreach ( $array as $key => $value ) {
281 - $new_key = $prefix ? $prefix . '.' . $key : $key;
282 - if ( is_array( $value ) ) {
283 - $result = array_merge( $result, $this->flatten_array_recursive( $value, $new_key ) );
284 - } else {
285 - $result[ $new_key ] = $value;
286 - }
287 - }
288 - return $result;
289 - }
290 -
291 - /**
292 - * Update a user
293 - */
294 - private function update_user( $config, $trigger_data ) {
295 - // Implementation logic...
296 - return array( 'success' => true );
297 - }
298 -
299 - /**
300 - * Delete a user
301 - */
302 - private function delete_user( $config, $trigger_data ) {
303 - // Implementation logic...
304 - return array( 'success' => true );
305 - }
306 -
307 - /**
308 - * Create a new post
309 - */
310 - private function create_post( $config, $trigger_data ) {
311 - $config_values = isset( $config['config'] ) ? $config['config'] : array();
312 - $action_id = isset( $config['actionId'] ) ? $config['actionId'] : 'wp_create_post';
313 -
314 - $post_title = isset( $config_values['post_title'] ) ? $this->parse_tokens( $config_values['post_title'], $trigger_data ) : '';
315 - $post_content = isset( $config_values['post_content'] ) ? $this->parse_tokens( $config_values['post_content'], $trigger_data ) : '';
316 - $post_type = isset( $config_values['post_type'] ) ? $config_values['post_type'] : ( $action_id === 'wp_create_page' ? 'page' : 'post' );
317 - $post_status = isset( $config_values['post_status'] ) ? $config_values['post_status'] : 'publish';
318 - $categories = isset( $config_values['categories'] ) ? $this->parse_tokens( $config_values['categories'], $trigger_data ) : '';
319 -
320 - if ( empty( $post_title ) || empty( $post_content ) ) {
321 - return array(
322 - 'success' => false,
323 - 'message' => 'Post Title and Content are required.',
324 - );
325 - }
326 -
327 - $post_data = array(
328 - 'post_title' => $post_title,
329 - 'post_content' => $post_content,
330 - 'post_status' => $post_status,
331 - 'post_type' => $post_type,
332 - );
333 -
334 - $post_id = wp_insert_post( $post_data );
335 -
336 - if ( is_wp_error( $post_id ) ) {
337 - return array(
338 - 'success' => false,
339 - 'message' => $post_id->get_error_message(),
340 - );
341 - }
342 -
343 - // Handle categories/topics if provided.
344 - if ( ! empty( $categories ) ) {
345 - $cat_ids = array_map( 'trim', explode( ',', $categories ) );
346 - wp_set_object_terms( $post_id, $cat_ids, 'category' );
347 - }
348 -
349 - return array(
350 - 'success' => true,
351 - 'post_id' => $post_id,
352 - 'message' => sprintf( '%s created successfully.', ucfirst( $post_type ) ),
353 - );
354 - }
355 -
356 - /**
357 - * Update a post
358 - */
359 - private function update_post( $config, $trigger_data ) {
360 - // Implementation logic...
361 - return array( 'success' => true );
362 - }
363 -
364 - /**
365 - * Create a new comment
366 - */
367 - private function create_comment( $config, $trigger_data ) {
368 - $config_values = isset( $config['config'] ) ? $config['config'] : array();
369 - $post_id = isset( $config_values['post_id'] ) ? $this->parse_tokens( $config_values['post_id'], $trigger_data ) : 0;
370 - $comment_content = isset( $config_values['comment_content'] ) ? $this->parse_tokens( $config_values['comment_content'], $trigger_data ) : '';
371 -
372 - if ( empty( $post_id ) ) {
373 - // Try to find a post ID in trigger data if not provided
374 - if ( isset( $trigger_data['post_id'] ) ) {
375 - $post_id = is_scalar( $trigger_data['post_id'] ) ? $trigger_data['post_id'] : 0;
376 - } elseif ( isset( $trigger_data['id'] ) && ( isset( $trigger_data['post_type'] ) || isset( $trigger_data['post_title'] ) ) ) {
377 - $post_id = is_scalar( $trigger_data['id'] ) ? $trigger_data['id'] : 0;
378 - }
379 - }
380 -
381 - // Fallback for content: use 'response' or 'content' from trigger (AI output)
382 - if ( empty( $comment_content ) ) {
383 - if ( isset( $trigger_data['response'] ) ) {
384 - $comment_content = is_scalar( $trigger_data['response'] ) ? (string) $trigger_data['response'] : wp_json_encode( $trigger_data['response'] );
385 - //error_log( "WPbot Automator - create_comment: Used fallback content from {response}" );
386 - } elseif ( isset( $trigger_data['content'] ) ) {
387 - $comment_content = is_scalar( $trigger_data['content'] ) ? (string) $trigger_data['content'] : wp_json_encode( $trigger_data['content'] );
388 - //error_log( "WPbot Automator - create_comment: Used fallback content from {content}" );
389 - }
390 - }
391 -
392 - if ( empty( $post_id ) ) {
393 - return array( 'success' => false, 'message' => 'Post ID is required.' );
394 - }
395 -
396 - if ( empty( $comment_content ) ) {
397 - return array( 'success' => false, 'message' => 'Comment Content is required.' );
398 - }
399 -
400 - $comment_id = wp_insert_comment( array(
401 - 'comment_post_ID' => absint( $post_id ),
402 - 'comment_content' => $comment_content,
403 - 'comment_type' => 'comment',
404 - 'comment_approved' => 1, // Auto-approve for now, or make configurable
405 - ) );
406 -
407 - if ( ! $comment_id ) {
408 - return array(
409 - 'success' => false,
410 - 'message' => 'Failed to create comment.',
411 - );
412 - }
413 -
414 - return array(
415 - 'success' => true,
416 - 'comment_id' => $comment_id,
417 - 'message' => 'Comment created successfully.',
418 - );
419 - }
420 -
421 - /**
422 - * AI Generated Post
423 - */
424 - private function ai_generated_post( $config, $trigger_data ) {
425 - $config_values = isset( $config['config'] ) ? $config['config'] : array();
426 - $instructions = isset( $config_values['prompt_instructions'] ) ? $this->parse_tokens( $config_values['prompt_instructions'], $trigger_data ) : '';
427 - $post_title = isset( $config_values['post_title'] ) ? $this->parse_tokens( $config_values['post_title'], $trigger_data ) : '';
428 -
429 - if ( empty( $instructions ) ) {
430 - return array( 'success' => false, 'message' => 'Instructions for AI are required.' );
431 - }
432 -
433 - // If title is empty, ask AI to generate one.
434 - if ( empty( $post_title ) ) {
435 - $prompt = "Generate a catchy title and a comprehensive blog post based on these instructions: {$instructions}. Return it in this format: [TITLE]Your Title[/TITLE][CONTENT]Your Content[/CONTENT]";
436 - } else {
437 - $prompt = "Generate a comprehensive blog post based on these instructions: {$instructions}. The title is already set to: {$post_title}";
438 - }
439 -
440 - $ai_response = $this->call_ai( $config_values, $trigger_data, $prompt, "You are a professional WordPress content creator." );
441 -
442 - if ( ! $ai_response['success'] ) {
443 - return $ai_response;
444 - }
445 -
446 - $content = $ai_response['content'];
447 -
448 - if ( empty( $post_title ) ) {
449 - if ( preg_match( '/\[TITLE\](.*?)\[\/TITLE\]/s', $content, $matches ) ) {
450 - $post_title = trim( $matches[1] );
451 - }
452 - if ( preg_match( '/\[CONTENT\](.*?)\[\/CONTENT\]/s', $content, $matches ) ) {
453 - $content = trim( $matches[1] );
454 - }
455 - }
456 -
457 - // Re-use create_post logic.
458 - $config['config']['post_title'] = $post_title;
459 - $config['config']['post_content'] = $content;
460 -
461 - return $this->create_post( $config, $trigger_data );
462 - }
463 -
464 - /**
465 - * AI Comment Reply
466 - */
467 - private function ai_reply_comment( $config, $trigger_data ) {
468 - $config_values = isset( $config['config'] ) ? $config['config'] : array();
469 - $parent_id = isset( $config_values['parent_id'] ) ? $this->parse_tokens( $config_values['parent_id'], $trigger_data ) : 0;
470 - $instructions = isset( $config_values['prompt_instructions'] ) ? $this->parse_tokens( $config_values['prompt_instructions'], $trigger_data ) : '';
471 -
472 - if ( empty( $parent_id ) && isset( $trigger_data['comment_id'] ) ) {
473 - $parent_id = $trigger_data['comment_id'];
474 - }
475 -
476 - if ( empty( $parent_id ) ) {
477 - return array( 'success' => false, 'message' => 'Parent Comment ID is required.' );
478 - }
479 -
480 - $parent_comment = get_comment( $parent_id );
481 - if ( ! $parent_comment ) {
482 - return array( 'success' => false, 'message' => 'Parent comment not found.' );
483 - }
484 -
485 - $parent_content = $parent_comment->comment_content;
486 - $prompt = "A user commented: \"{$parent_content}\". Generate a reply based on these instructions: {$instructions}";
487 -
488 - $ai_response = $this->call_ai( $config_values, $trigger_data, $prompt, "You are a helpful and polite WordPress site administrator." );
489 -
490 - if ( ! $ai_response['success'] ) {
491 - return $ai_response;
492 - }
493 -
494 - // Re-use reply_comment logic.
495 - $config['config']['parent_id'] = $parent_id;
496 - $config['config']['comment_content'] = $ai_response['content'];
497 -
498 - return $this->reply_comment( $config, $trigger_data );
499 - }
500 -
501 - /**
502 - * AI Generate Social Media Posts
503 - */
504 - private function ai_generate_social_posts( $config, $trigger_data ) {
505 - $config_values = isset( $config['config'] ) ? $config['config'] : array();
506 - $action_id = isset( $config['actionId'] ) ? $config['actionId'] : 'wp_ai_generate_social_posts';
507 - $source_content = isset( $config_values['source_content'] ) ? $this->parse_tokens( $config_values['source_content'], $trigger_data ) : '';
508 - $instructions = isset( $config_values['additional_instructions'] ) ? $this->parse_tokens( $config_values['additional_instructions'], $trigger_data ) : '';
509 -
510 - if ( empty( $source_content ) ) {
511 - // Fallback: try to find post content in trigger data.
512 - if ( isset( $trigger_data['post_content'] ) ) {
513 - $source_content = $trigger_data['post_content'];
514 - } else {
515 - return array( 'success' => false, 'message' => 'Source Content is required.' );
516 - }
517 - }
518 -
519 - $is_pro = ( $action_id === 'wp_ai_generate_social_posts_pro' );
520 -
521 - if ( $is_pro ) {
522 - $prompt = "You are a social media expert. Based on the following content, generate three optimized posts for Instagram, WhatsApp, and LinkedIn.
523 - Additional Instructions: {$instructions}
524 -
525 - Content:
526 - \"{$source_content}\"
527 -
528 - IMPORTANT: You MUST return the output in EXACTLY this format:
529 - [INSTAGRAM]
530 - (Your Instagram post content here)
531 - [/INSTAGRAM]
532 - [WHATSAPP]
533 - (Your WhatsApp post content here)
534 - [/WHATSAPP]
535 - [LINKEDIN]
536 - (Your LinkedIn post content here)
537 - [/LINKEDIN]";
538 - } else {
539 - $prompt = "You are a social media expert. Based on the following content, generate two optimized posts for Facebook and Telegram.
540 - Additional Instructions: {$instructions}
541 -
542 - Content:
543 - \"{$source_content}\"
544 -
545 - IMPORTANT: You MUST return the output in EXACTLY this format:
546 - [FACEBOOK]
547 - (Your Facebook post content here)
548 - [/FACEBOOK]
549 - [TELEGRAM]
550 - (Your Telegram post content here)
551 - [/TELEGRAM]";
552 - }
553 -
554 - $ai_response = $this->call_ai( $config_values, $trigger_data, $prompt, "You are a helpful social media manager." );
555 -
556 - if ( ! $ai_response['success'] ) {
557 - return $ai_response;
558 - }
559 -
560 - $content = $ai_response['content'];
561 - $results = array(
562 - 'success' => true,
563 - 'fb_post' => '',
564 - 'telegram_post' => '',
565 - 'insta_post' => '',
566 - 'whatsapp_post' => '',
567 - 'linkedin_post' => '',
568 - 'content' => $content, // Store full response just in case
569 - );
570 -
571 - if ( preg_match( '/\[FACEBOOK\](.*?)\[\/FACEBOOK\]/s', $content, $matches ) ) {
572 - $results['fb_post'] = trim( $matches[1] );
573 - }
574 - if ( preg_match( '/\[TELEGRAM\](.*?)\[\/TELEGRAM\]/s', $content, $matches ) ) {
575 - $results['telegram_post'] = trim( $matches[1] );
576 - }
577 - if ( preg_match( '/\[INSTAGRAM\](.*?)\[\/INSTAGRAM\]/s', $content, $matches ) ) {
578 - $results['insta_post'] = trim( $matches[1] );
579 - }
580 - if ( preg_match( '/\[WHATSAPP\](.*?)\[\/WHATSAPP\]/s', $content, $matches ) ) {
581 - $results['whatsapp_post'] = trim( $matches[1] );
582 - }
583 - if ( preg_match( '/\[LINKEDIN\](.*?)\[\/LINKEDIN\]/s', $content, $matches ) ) {
584 - $results['linkedin_post'] = trim( $matches[1] );
585 - }
586 -
587 - // AI Image Generation
588 - if ( ! empty( $config_values['generate_image'] ) && $config_values['generate_image'] !== "false" ) {
589 - // 1. Generate an image prompt using the text AI
590 - $image_prompt_request = "Based on the following content, generate a concise, high-quality image generation prompt (max 100 words) that would be suitable for a social media post. Focus on visual details.
591 -
592 - Content:
593 - \"{$source_content}\"";
594 -
595 - $image_prompt_response = $this->call_ai( $config_values, $trigger_data, $image_prompt_request, "You are a creative visual designer." );
596 -
597 - if ( $image_prompt_response['success'] ) {
598 - $image_prompt = $image_prompt_response['content'];
599 -
600 - // 2. Call OpenAI DALL-E
601 - $image_result = $this->call_openai_image( $config_values, $trigger_data, $image_prompt );
602 -
603 - if ( $image_result['success'] ) {
604 - $results['generated_image_url'] = $image_result['url'];
605 - } else {
606 - $results['image_error'] = $image_result['message'];
607 - }
608 - }
609 - }
610 -
611 - return $results;
612 - }
613 -
614 - /**
615 - * Call OpenAI Image (DALL-E) API helper
616 - */
617 - private function call_openai_image( $config_values, $trigger_data, $prompt ) {
618 - $api_key = isset( $config_values['api_key'] ) ? $this->parse_tokens( $config_values['api_key'], $trigger_data ) : '';
619 - $model = isset( $config_values['image_model'] ) ? $config_values['image_model'] : 'dall-e-3';
620 - $size = isset( $config_values['image_size'] ) ? $config_values['image_size'] : '1024x1024';
621 - $style = isset( $config_values['image_style'] ) ? $config_values['image_style'] : 'vivid';
622 -
623 - if ( empty( $api_key ) ) {
624 - return array( 'success' => false, 'message' => 'API Key is required for image generation.' );
625 - }
626 -
627 - $url = 'https://api.openai.com/v1/images/generations';
628 -
629 - $body = array(
630 - 'model' => $model,
631 - 'prompt' => $prompt,
632 - 'n' => 1,
633 - 'size' => $size,
634 - );
635 -
636 - if ( $model === 'dall-e-3' ) {
637 - $body['style'] = $style;
638 - }
639 -
640 - $args = array(
641 - 'method' => 'POST',
642 - 'headers' => array(
643 - 'Content-Type' => 'application/json',
644 - 'Authorization' => 'Bearer ' . $api_key,
645 - ),
646 - 'body' => wp_json_encode( $body ),
647 - 'timeout' => 90,
648 - );
649 -
650 - $response = wp_remote_request( $url, $args );
651 -
652 - if ( is_wp_error( $response ) ) {
653 - return array( 'success' => false, 'message' => $response->get_error_message() );
654 - }
655 -
656 - $response_code = wp_remote_retrieve_response_code( $response );
657 - $response_body = wp_remote_retrieve_body( $response );
658 - $data = json_decode( $response_body, true );
659 -
660 - if ( $response_code >= 200 && $response_code < 300 ) {
661 - return array(
662 - 'success' => true,
663 - 'url' => isset( $data['data'][0]['url'] ) ? $data['data'][0]['url'] : '',
664 - 'data' => $data,
665 - );
666 - } else {
667 - $err = isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown OpenAI Image error';
668 - return array( 'success' => false, 'message' => $err, 'code' => $response_code );
669 - }
670 - }
671 -
672 - /**
673 - * Call AI Provider helper
674 - */
675 - private function call_ai( $config_values, $trigger_data, $prompt, $system_prompt = '' ) {
676 - $provider = isset( $config_values['ai_provider'] ) ? $config_values['ai_provider'] : 'openai';
677 -
678 - switch ( $provider ) {
679 - case 'gemini':
680 - return $this->call_gemini( $config_values, $trigger_data, $prompt, $system_prompt );
681 - case 'claude':
682 - return $this->call_claude( $config_values, $trigger_data, $prompt, $system_prompt );
683 - case 'openai':
684 - default:
685 - return $this->call_openai( $config_values, $trigger_data, $prompt, $system_prompt );
686 - }
687 - }
688 -
689 - /**
690 - * Call OpenAI API helper
691 - */
692 - private function call_openai( $config_values, $trigger_data, $prompt, $system_prompt = '' ) {
693 - $api_key = isset( $config_values['api_key'] ) ? $this->parse_tokens( $config_values['api_key'], $trigger_data ) : '';
694 - $model = isset( $config_values['model'] ) ? $config_values['model'] : 'gpt-3.5-turbo';
695 - $max_tokens = isset( $config_values['max_tokens'] ) ? intval( $config_values['max_tokens'] ) : 1500;
696 - $temperature = isset( $config_values['temperature'] ) ? floatval( $config_values['temperature'] ) : 0.7;
697 -
698 - if ( empty( $api_key ) ) {
699 - return array( 'success' => false, 'message' => 'OpenAI API Key is required.' );
700 - }
701 -
702 - $messages = array();
703 - if ( ! empty( $system_prompt ) ) {
704 - $messages[] = array( 'role' => 'system', 'content' => $system_prompt );
705 - }
706 - $messages[] = array( 'role' => 'user', 'content' => $prompt );
707 -
708 - $url = 'https://api.openai.com/v1/chat/completions';
709 - $args = array(
710 - 'method' => 'POST',
711 - 'headers' => array(
712 - 'Content-Type' => 'application/json',
713 - 'Authorization' => 'Bearer ' . $api_key,
714 - ),
715 - 'body' => wp_json_encode( array(
716 - 'model' => $model,
717 - 'messages' => $messages,
718 - 'max_tokens' => $max_tokens,
719 - 'temperature' => $temperature,
720 - ) ),
721 - 'timeout' => 60,
722 - );
723 -
724 - $response = wp_remote_request( $url, $args );
725 -
726 - if ( is_wp_error( $response ) ) {
727 - return array( 'success' => false, 'message' => $response->get_error_message() );
728 - }
729 -
730 - $response_code = wp_remote_retrieve_response_code( $response );
731 - $response_body = wp_remote_retrieve_body( $response );
732 - $data = json_decode( $response_body, true );
733 -
734 - if ( $response_code >= 200 && $response_code < 300 ) {
735 - return array(
736 - 'success' => true,
737 - 'content' => isset( $data['choices'][0]['message']['content'] ) ? $data['choices'][0]['message']['content'] : '',
738 - 'data' => $data,
739 - );
740 - } else {
741 - $err = isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown OpenAI error';
742 - return array( 'success' => false, 'message' => $err, 'code' => $response_code );
743 - }
744 - }
745 -
746 - /**
747 - * Call Gemini API helper
748 - */
749 - private function call_gemini( $config_values, $trigger_data, $prompt, $system_prompt = '' ) {
750 - $api_key = isset( $config_values['api_key'] ) ? $this->parse_tokens( $config_values['api_key'], $trigger_data ) : '';
751 - $model = isset( $config_values['model'] ) ? $config_values['model'] : 'gemini-1.5-flash';
752 - $temperature = isset( $config_values['temperature'] ) ? floatval( $config_values['temperature'] ) : 0.7;
753 -
754 - if ( empty( $api_key ) ) {
755 - return array( 'success' => false, 'message' => 'Gemini API Key is required.' );
756 - }
757 -
758 - $full_prompt = $system_prompt ? $system_prompt . "\n\n" . $prompt : $prompt;
759 -
760 - $url = 'https://generativelanguage.googleapis.com/v1/models/' . $model . ':generateContent?key=' . $api_key;
761 -
762 - $body = array(
763 - 'contents' => array(
764 - array(
765 - 'parts' => array(
766 - array( 'text' => $full_prompt ),
767 - ),
768 - ),
769 - ),
770 - 'generationConfig' => array(
771 - 'temperature' => $temperature,
772 - ),
773 - );
774 -
775 - $args = array(
776 - 'method' => 'POST',
777 - 'headers' => array( 'Content-Type' => 'application/json' ),
778 - 'body' => wp_json_encode( $body ),
779 - 'timeout' => 60,
780 - );
781 -
782 - $response = wp_remote_request( $url, $args );
783 -
784 - if ( is_wp_error( $response ) ) {
785 - return array( 'success' => false, 'message' => $response->get_error_message() );
786 - }
787 -
788 - $response_code = wp_remote_retrieve_response_code( $response );
789 - $response_body = wp_remote_retrieve_body( $response );
790 - $data = json_decode( $response_body, true );
791 -
792 - if ( $response_code >= 200 && $response_code < 300 ) {
793 - $content = isset( $data['candidates'][0]['content']['parts'][0]['text'] ) ? $data['candidates'][0]['content']['parts'][0]['text'] : '';
794 - return array(
795 - 'success' => true,
796 - 'content' => $content,
797 - 'data' => $data,
798 - );
799 - } else {
800 - $err = isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown Gemini error';
801 - return array( 'success' => false, 'message' => $err, 'code' => $response_code );
802 - }
803 - }
804 -
805 - /**
806 - * Call Claude API helper
807 - */
808 - private function call_claude( $config_values, $trigger_data, $prompt, $system_prompt = '' ) {
809 - $api_key = isset( $config_values['api_key'] ) ? $this->parse_tokens( $config_values['api_key'], $trigger_data ) : '';
810 - $model = isset( $config_values['model'] ) ? $config_values['model'] : 'claude-3-5-sonnet-20240620';
811 - $max_tokens = isset( $config_values['max_tokens'] ) ? intval( $config_values['max_tokens'] ) : 1500;
812 - $temperature = isset( $config_values['temperature'] ) ? floatval( $config_values['temperature'] ) : 0.7;
813 -
814 - if ( empty( $api_key ) ) {
815 - return array( 'success' => false, 'message' => 'Claude API Key is required.' );
816 - }
817 -
818 - $url = 'https://api.anthropic.com/v1/messages';
819 -
820 - $body = array(
821 - 'model' => $model,
822 - 'messages' => array(
823 - array( 'role' => 'user', 'content' => $prompt ),
824 - ),
825 - 'max_tokens' => $max_tokens,
826 - 'temperature' => $temperature,
827 - );
828 -
829 - if ( ! empty( $system_prompt ) ) {
830 - $body['system'] = $system_prompt;
831 - }
832 -
833 - $args = array(
834 - 'method' => 'POST',
835 - 'headers' => array(
836 - 'Content-Type' => 'application/json',
837 - 'x-api-key' => $api_key,
838 - 'anthropic-version' => '2023-06-01',
839 - ),
840 - 'body' => wp_json_encode( $body ),
841 - 'timeout' => 60,
842 - );
843 -
844 - $response = wp_remote_request( $url, $args );
845 -
846 - if ( is_wp_error( $response ) ) {
847 - return array( 'success' => false, 'message' => $response->get_error_message() );
848 - }
849 -
850 - $response_code = wp_remote_retrieve_response_code( $response );
851 - $response_body = wp_remote_retrieve_body( $response );
852 - $data = json_decode( $response_body, true );
853 -
854 - if ( $response_code >= 200 && $response_code < 300 ) {
855 - $content = isset( $data['content'][0]['text'] ) ? $data['content'][0]['text'] : '';
856 - return array(
857 - 'success' => true,
858 - 'content' => $content,
859 - 'data' => $data,
860 - );
861 - } else {
862 - $err = isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown Claude error';
863 - return array( 'success' => false, 'message' => $err, 'code' => $response_code );
864 - }
865 - }
866 -}
1 +<?php
2 +/**
3 + * WordPress Actions
4 + *
5 + * @package WPbot_Automator
6 + */
7 +
8 +namespace WPbot_Automator\Actions;
9 +
10 +if ( ! defined( 'ABSPATH' ) ) {
11 + exit;
12 +}
13 +
14 +/**
15 + * Class WordPress_Actions
16 + */
17 +class WordPress_Actions extends Action {
18 +
19 + /**
20 + * Constructor
21 + */
22 + public function __construct() {
23 + $this->id = 'wordpress';
24 + $this->group = __( 'WordPress', 'wpbot-automator' );
25 + }
26 +
27 + /**
28 + * Execute the action
29 + *
30 + * @param array $action_data Configuration for this action from the workflow.
31 + * @param array $trigger_data Data passed from the trigger.
32 + * @return bool|array
33 + */
34 + public function execute( $action_data, $trigger_data ) {
35 + $action_id = isset( $action_data['actionId'] ) ? $action_data['actionId'] : '';
36 +
37 + if ( empty( $action_id ) ) {
38 + return false;
39 + }
40 +
41 + switch ( $action_id ) {
42 + case 'wp_create_user':
43 + case 'wp_user_register':
44 + return $this->create_user( $action_data, $trigger_data );
45 + case 'wp_update_user':
46 + return $this->update_user( $action_data, $trigger_data );
47 + case 'wp_delete_user':
48 + return $this->delete_user( $action_data, $trigger_data );
49 + case 'wp_create_post':
50 + case 'wp_create_page':
51 + return $this->create_post( $action_data, $trigger_data );
52 + case 'wp_update_post':
53 + return $this->update_post( $action_data, $trigger_data );
54 + case 'wp_create_comment':
55 + return $this->create_comment( $action_data, $trigger_data );
56 + case 'wp_reply_comment':
57 + return $this->reply_comment( $action_data, $trigger_data );
58 + case 'wp_ai_generated_post':
59 + return $this->ai_generated_post( $action_data, $trigger_data );
60 + case 'wp_ai_reply_comment':
61 + return $this->ai_reply_comment( $action_data, $trigger_data );
62 + case 'wp_ai_generate_social_posts':
63 + case 'wp_ai_generate_social_posts_pro':
64 + return $this->ai_generate_social_posts( $action_data, $trigger_data );
65 + // Add more cases here as needed...
66 + default:
67 + // Fallback for actions not yet fully implemented
68 + return array( 'success' => true, 'message' => "Action {$action_id} executed (simulated)" );
69 + }
70 + }
71 +
72 + /**
73 + * Reply to a comment
74 + */
75 + private function reply_comment( $config, $trigger_data ) {
76 + $config_values = isset( $config['config'] ) ? $config['config'] : array();
77 + $parent_id = isset( $config_values['parent_id'] ) ? $this->parse_tokens( $config_values['parent_id'], $trigger_data ) : 0;
78 + $comment_content = isset( $config_values['comment_content'] ) ? $this->parse_tokens( $config_values['comment_content'], $trigger_data ) : '';
79 +
80 + // Fallback for parent_id: use comment_id from trigger if available
81 + if ( empty( $parent_id ) && isset( $trigger_data['comment_id'] ) ) {
82 + $parent_id = is_scalar( $trigger_data['comment_id'] ) ? $trigger_data['comment_id'] : 0;
83 + //error_log( "WPbot Automator - reply_comment: Used fallback parent_id: " . $parent_id );
84 + }
85 +
86 + // Fallback for content: use 'response' or 'content' from trigger (AI output)
87 + if ( empty( $comment_content ) ) {
88 + if ( isset( $trigger_data['response'] ) ) {
89 + $comment_content = is_scalar( $trigger_data['response'] ) ? (string) $trigger_data['response'] : wp_json_encode( $trigger_data['response'] );
90 + //error_log( "WPbot Automator - reply_comment: Used fallback content from {response}" );
91 + } elseif ( isset( $trigger_data['content'] ) ) {
92 + $comment_content = is_scalar( $trigger_data['content'] ) ? (string) $trigger_data['content'] : wp_json_encode( $trigger_data['content'] );
93 + //error_log( "WPbot Automator - reply_comment: Used fallback content from {content}" );
94 + }
95 + }
96 +
97 + if ( empty( $parent_id ) ) {
98 + return array( 'success' => false, 'message' => 'Parent Comment ID is required.' );
99 + }
100 +
101 + if ( empty( $comment_content ) ) {
102 + return array( 'success' => false, 'message' => 'Reply Content is required.' );
103 + }
104 +
105 + $parent_comment = get_comment( $parent_id );
106 + if ( ! $parent_comment ) {
107 + return array(
108 + 'success' => false,
109 + 'message' => 'Parent comment not found for ID: ' . $parent_id,
110 + );
111 + }
112 +
113 + $comment_id = wp_insert_comment( array(
114 + 'comment_post_ID' => $parent_comment->comment_post_ID,
115 + 'comment_parent' => absint( $parent_id ),
116 + 'comment_content' => $comment_content,
117 + 'comment_type' => 'comment',
118 + 'comment_approved' => 1,
119 + ) );
120 +
121 + if ( ! $comment_id ) {
122 + return array(
123 + 'success' => false,
124 + 'message' => 'Failed to create reply.',
125 + );
126 + }
127 +
128 + return array(
129 + 'success' => true,
130 + 'comment_id' => $comment_id,
131 + 'message' => 'Reply created successfully.',
132 + );
133 + }
134 +
135 + /**
136 + * Create a new user
137 + */
138 + private function create_user( $config, $trigger_data ) {
139 + $config_values = isset( $config['config'] ) ? $config['config'] : array();
140 + $user_login = isset( $config_values['user_login'] ) ? $this->parse_tokens( $config_values['user_login'], $trigger_data ) : '';
141 + $user_email = isset( $config_values['user_email'] ) ? $this->parse_tokens( $config_values['user_email'], $trigger_data ) : '';
142 +
143 + // Smart Mapping Fallback: If not provided, try to find in trigger_data.
144 + if ( empty( $user_email ) ) {
145 + $user_email = $this->find_email_in_data( $trigger_data );
146 + }
147 +
148 + if ( empty( $user_login ) ) {
149 + // Try to find a name or use first part of email.
150 + $name = $this->find_name_in_data( $trigger_data );
151 + $user_login = ! empty( $name ) ? sanitize_user( strtolower( str_replace( ' ', '.', $name ) ) ) : ( ! empty( $user_email ) ? explode( '@', $user_email )[0] : '' );
152 +
153 + // Safety: WordPress usernames have a 60 character limit.
154 + if ( strlen( $user_login ) > 60 ) {
155 + $user_login = substr( $user_login, 0, 60 );
156 + }
157 + }
158 +
159 + $user_pass = isset( $config_values['user_pass'] ) ? $this->parse_tokens( $config_values['user_pass'], $trigger_data ) : wp_generate_password();
160 + $role = isset( $config_values['role'] ) ? $config_values['role'] : 'subscriber';
161 +
162 + if ( empty( $user_login ) || empty( $user_email ) ) {
163 + return array(
164 + 'success' => false,
165 + 'message' => sprintf( 'User login and email are required. Found Login: "%s", Email: "%s"', $user_login, $user_email ),
166 + );
167 + }
168 +
169 + if ( username_exists( $user_login ) ) {
170 + return array(
171 + 'success' => false,
172 + 'message' => "Username already exists: $user_login",
173 + );
174 + }
175 +
176 + if ( email_exists( $user_email ) ) {
177 + return array(
178 + 'success' => false,
179 + 'message' => "Email already exists: $user_email",
180 + );
181 + }
182 +
183 + $user_id = wp_insert_user( array(
184 + 'user_login' => $user_login,
185 + 'user_email' => $user_email,
186 + 'user_pass' => $user_pass,
187 + 'role' => $role,
188 + ) );
189 +
190 + if ( is_wp_error( $user_id ) ) {
191 + return array(
192 + 'success' => false,
193 + 'message' => $user_id->get_error_message(),
194 + );
195 + }
196 +
197 + return array(
198 + 'success' => true,
199 + 'user_id' => $user_id,
200 + 'message' => 'User created successfully.',
201 + );
202 + }
203 +
204 + /**
205 + * Helper: Find email in trigger data
206 + */
207 + private function find_email_in_data( $data ) {
208 + if ( ! is_array( $data ) ) return '';
209 +
210 + // Flatten and look for keys containing 'email'.
211 + $flattened = $this->flatten_array_recursive( $data );
212 + foreach ( $flattened as $key => $val ) {
213 + if ( is_string( $val ) && is_email( $val ) ) {
214 + return $val;
215 + }
216 + }
217 + return '';
218 + }
219 +
220 + /**
221 + * Helper: Find name in trigger data
222 + */
223 + private function find_name_in_data( $data ) {
224 + if ( ! is_array( $data ) ) return '';
225 +
226 + $flattened = $this->flatten_array_recursive( $data );
227 +
228 + $firstName = '';
229 + $lastName = '';
230 + $fullName = '';
231 +
232 + foreach ( $flattened as $key => $val ) {
233 + if ( ! is_string( $val ) || empty( $val ) ) continue;
234 + $low_key = strtolower( $key );
235 +
236 + // Priority 1: Explicit sub-fields (common in WPForms, etc).
237 + if ( strpos( $low_key, '.first' ) !== false || strpos( $low_key, 'first_name' ) !== false ) {
238 + $firstName = $val;
239 + }
240 + if ( strpos( $low_key, '.last' ) !== false || strpos( $low_key, 'last_name' ) !== false ) {
241 + $lastName = $val;
242 + }
243 +
244 + // Priority 2: Generic name field.
245 + if ( $low_key === 'fullname' || $low_key === 'full_name' || ( strpos( $low_key, 'name' ) !== false && strpos( $low_key, '.value' ) !== false ) ) {
246 + $fullName = $val;
247 + }
248 + }
249 +
250 + if ( ! empty( $firstName ) || ! empty( $lastName ) ) {
251 + return trim( $firstName . ' ' . $lastName );
252 + }
253 +
254 + if ( ! empty( $fullName ) ) {
255 + return $fullName;
256 + }
257 +
258 + // Fallback: Just the FIRST thing that looks like a name.
259 + foreach ( $flattened as $key => $val ) {
260 + $low_key = strtolower( $key );
261 + if ( strpos( $low_key, 'name' ) !== false ) {
262 + // Avoid labels.
263 + if ( is_string( $val ) && strlen( $val ) > 2 && strlen( $val ) < 50 ) {
264 + $low_val = strtolower( trim( $val ) );
265 + if ( ! in_array( $low_val, array( 'name', 'first name', 'last name', 'full name', 'email', 'message', 'subject' ) ) ) {
266 + return $val;
267 + }
268 + }
269 + }
270 + }
271 +
272 + return '';
273 + }
274 +
275 + /**
276 + * Helper to flatten array recursively
277 + */
278 + private function flatten_array_recursive( $array, $prefix = '' ) {
279 + $result = array();
280 + foreach ( $array as $key => $value ) {
281 + $new_key = $prefix ? $prefix . '.' . $key : $key;
282 + if ( is_array( $value ) ) {
283 + $result = array_merge( $result, $this->flatten_array_recursive( $value, $new_key ) );
284 + } else {
285 + $result[ $new_key ] = $value;
286 + }
287 + }
288 + return $result;
289 + }
290 +
291 + /**
292 + * Update a user
293 + */
294 + private function update_user( $config, $trigger_data ) {
295 + // Implementation logic...
296 + return array( 'success' => true );
297 + }
298 +
299 + /**
300 + * Delete a user
301 + */
302 + private function delete_user( $config, $trigger_data ) {
303 + // Implementation logic...
304 + return array( 'success' => true );
305 + }
306 +
307 + /**
308 + * Create a new post
309 + */
310 + private function create_post( $config, $trigger_data ) {
311 + $config_values = isset( $config['config'] ) ? $config['config'] : array();
312 + $action_id = isset( $config['actionId'] ) ? $config['actionId'] : 'wp_create_post';
313 +
314 + $post_title = isset( $config_values['post_title'] ) ? $this->parse_tokens( $config_values['post_title'], $trigger_data ) : '';
315 + $post_content = isset( $config_values['post_content'] ) ? $this->parse_tokens( $config_values['post_content'], $trigger_data ) : '';
316 + $post_type = isset( $config_values['post_type'] ) ? $config_values['post_type'] : ( $action_id === 'wp_create_page' ? 'page' : 'post' );
317 + $post_status = isset( $config_values['post_status'] ) ? $config_values['post_status'] : 'publish';
318 + $categories = isset( $config_values['categories'] ) ? $this->parse_tokens( $config_values['categories'], $trigger_data ) : '';
319 +
320 + if ( empty( $post_title ) || empty( $post_content ) ) {
321 + return array(
322 + 'success' => false,
323 + 'message' => 'Post Title and Content are required.',
324 + );
325 + }
326 +
327 + $post_data = array(
328 + 'post_title' => $post_title,
329 + 'post_content' => $post_content,
330 + 'post_status' => $post_status,
331 + 'post_type' => $post_type,
332 + );
333 +
334 + $post_id = wp_insert_post( $post_data );
335 +
336 + if ( is_wp_error( $post_id ) ) {
337 + return array(
338 + 'success' => false,
339 + 'message' => $post_id->get_error_message(),
340 + );
341 + }
342 +
343 + // Handle categories/topics if provided.
344 + if ( ! empty( $categories ) ) {
345 + $cat_ids = array_map( 'trim', explode( ',', $categories ) );
346 + wp_set_object_terms( $post_id, $cat_ids, 'category' );
347 + }
348 +
349 + return array(
350 + 'success' => true,
351 + 'post_id' => $post_id,
352 + 'message' => sprintf( '%s created successfully.', ucfirst( $post_type ) ),
353 + );
354 + }
355 +
356 + /**
357 + * Update a post
358 + */
359 + private function update_post( $config, $trigger_data ) {
360 + // Implementation logic...
361 + return array( 'success' => true );
362 + }
363 +
364 + /**
365 + * Create a new comment
366 + */
367 + private function create_comment( $config, $trigger_data ) {
368 + $config_values = isset( $config['config'] ) ? $config['config'] : array();
369 + $post_id = isset( $config_values['post_id'] ) ? $this->parse_tokens( $config_values['post_id'], $trigger_data ) : 0;
370 + $comment_content = isset( $config_values['comment_content'] ) ? $this->parse_tokens( $config_values['comment_content'], $trigger_data ) : '';
371 +
372 + if ( empty( $post_id ) ) {
373 + // Try to find a post ID in trigger data if not provided
374 + if ( isset( $trigger_data['post_id'] ) ) {
375 + $post_id = is_scalar( $trigger_data['post_id'] ) ? $trigger_data['post_id'] : 0;
376 + } elseif ( isset( $trigger_data['id'] ) && ( isset( $trigger_data['post_type'] ) || isset( $trigger_data['post_title'] ) ) ) {
377 + $post_id = is_scalar( $trigger_data['id'] ) ? $trigger_data['id'] : 0;
378 + }
379 + }
380 +
381 + // Fallback for content: use 'response' or 'content' from trigger (AI output)
382 + if ( empty( $comment_content ) ) {
383 + if ( isset( $trigger_data['response'] ) ) {
384 + $comment_content = is_scalar( $trigger_data['response'] ) ? (string) $trigger_data['response'] : wp_json_encode( $trigger_data['response'] );
385 + //error_log( "WPbot Automator - create_comment: Used fallback content from {response}" );
386 + } elseif ( isset( $trigger_data['content'] ) ) {
387 + $comment_content = is_scalar( $trigger_data['content'] ) ? (string) $trigger_data['content'] : wp_json_encode( $trigger_data['content'] );
388 + //error_log( "WPbot Automator - create_comment: Used fallback content from {content}" );
389 + }
390 + }
391 +
392 + if ( empty( $post_id ) ) {
393 + return array( 'success' => false, 'message' => 'Post ID is required.' );
394 + }
395 +
396 + if ( empty( $comment_content ) ) {
397 + return array( 'success' => false, 'message' => 'Comment Content is required.' );
398 + }
399 +
400 + $comment_id = wp_insert_comment( array(
401 + 'comment_post_ID' => absint( $post_id ),
402 + 'comment_content' => $comment_content,
403 + 'comment_type' => 'comment',
404 + 'comment_approved' => 1, // Auto-approve for now, or make configurable
405 + ) );
406 +
407 + if ( ! $comment_id ) {
408 + return array(
409 + 'success' => false,
410 + 'message' => 'Failed to create comment.',
411 + );
412 + }
413 +
414 + return array(
415 + 'success' => true,
416 + 'comment_id' => $comment_id,
417 + 'message' => 'Comment created successfully.',
418 + );
419 + }
420 +
421 + /**
422 + * AI Generated Post
423 + */
424 + private function ai_generated_post( $config, $trigger_data ) {
425 + $config_values = isset( $config['config'] ) ? $config['config'] : array();
426 + $instructions = isset( $config_values['prompt_instructions'] ) ? $this->parse_tokens( $config_values['prompt_instructions'], $trigger_data ) : '';
427 + $post_title = isset( $config_values['post_title'] ) ? $this->parse_tokens( $config_values['post_title'], $trigger_data ) : '';
428 +
429 + if ( empty( $instructions ) ) {
430 + return array( 'success' => false, 'message' => 'Instructions for AI are required.' );
431 + }
432 +
433 + // If title is empty, ask AI to generate one.
434 + if ( empty( $post_title ) ) {
435 + $prompt = "Generate a catchy title and a comprehensive blog post based on these instructions: {$instructions}. Return it in this format: [TITLE]Your Title[/TITLE][CONTENT]Your Content[/CONTENT]";
436 + } else {
437 + $prompt = "Generate a comprehensive blog post based on these instructions: {$instructions}. The title is already set to: {$post_title}";
438 + }
439 +
440 + $ai_response = $this->call_ai( $config_values, $trigger_data, $prompt, "You are a professional WordPress content creator." );
441 +
442 + if ( ! $ai_response['success'] ) {
443 + return $ai_response;
444 + }
445 +
446 + $content = $ai_response['content'];
447 +
448 + if ( empty( $post_title ) ) {
449 + if ( preg_match( '/\[TITLE\](.*?)\[\/TITLE\]/s', $content, $matches ) ) {
450 + $post_title = trim( $matches[1] );
451 + }
452 + if ( preg_match( '/\[CONTENT\](.*?)\[\/CONTENT\]/s', $content, $matches ) ) {
453 + $content = trim( $matches[1] );
454 + }
455 + }
456 +
457 + // Re-use create_post logic.
458 + $config['config']['post_title'] = $post_title;
459 + $config['config']['post_content'] = $content;
460 +
461 + return $this->create_post( $config, $trigger_data );
462 + }
463 +
464 + /**
465 + * AI Comment Reply
466 + */
467 + private function ai_reply_comment( $config, $trigger_data ) {
468 + $config_values = isset( $config['config'] ) ? $config['config'] : array();
469 + $parent_id = isset( $config_values['parent_id'] ) ? $this->parse_tokens( $config_values['parent_id'], $trigger_data ) : 0;
470 + $instructions = isset( $config_values['prompt_instructions'] ) ? $this->parse_tokens( $config_values['prompt_instructions'], $trigger_data ) : '';
471 +
472 + if ( empty( $parent_id ) && isset( $trigger_data['comment_id'] ) ) {
473 + $parent_id = $trigger_data['comment_id'];
474 + }
475 +
476 + if ( empty( $parent_id ) ) {
477 + return array( 'success' => false, 'message' => 'Parent Comment ID is required.' );
478 + }
479 +
480 + $parent_comment = get_comment( $parent_id );
481 + if ( ! $parent_comment ) {
482 + return array( 'success' => false, 'message' => 'Parent comment not found.' );
483 + }
484 +
485 + $parent_content = $parent_comment->comment_content;
486 + $prompt = "A user commented: \"{$parent_content}\". Generate a reply based on these instructions: {$instructions}";
487 +
488 + $ai_response = $this->call_ai( $config_values, $trigger_data, $prompt, "You are a helpful and polite WordPress site administrator." );
489 +
490 + if ( ! $ai_response['success'] ) {
491 + return $ai_response;
492 + }
493 +
494 + // Re-use reply_comment logic.
495 + $config['config']['parent_id'] = $parent_id;
496 + $config['config']['comment_content'] = $ai_response['content'];
497 +
498 + return $this->reply_comment( $config, $trigger_data );
499 + }
500 +
501 + /**
502 + * AI Generate Social Media Posts
503 + */
504 + private function ai_generate_social_posts( $config, $trigger_data ) {
505 + $config_values = isset( $config['config'] ) ? $config['config'] : array();
506 + $action_id = isset( $config['actionId'] ) ? $config['actionId'] : 'wp_ai_generate_social_posts';
507 + $source_content = isset( $config_values['source_content'] ) ? $this->parse_tokens( $config_values['source_content'], $trigger_data ) : '';
508 + $instructions = isset( $config_values['additional_instructions'] ) ? $this->parse_tokens( $config_values['additional_instructions'], $trigger_data ) : '';
509 +
510 + if ( empty( $source_content ) ) {
511 + // Fallback: try to find post content in trigger data.
512 + if ( isset( $trigger_data['post_content'] ) ) {
513 + $source_content = $trigger_data['post_content'];
514 + } else {
515 + return array( 'success' => false, 'message' => 'Source Content is required.' );
516 + }
517 + }
518 +
519 + $is_pro = ( $action_id === 'wp_ai_generate_social_posts_pro' );
520 +
521 + if ( $is_pro ) {
522 + $prompt = "You are a social media expert. Based on the following content, generate three optimized posts for Instagram, WhatsApp, and LinkedIn.
523 + Additional Instructions: {$instructions}
524 +
525 + Content:
526 + \"{$source_content}\"
527 +
528 + IMPORTANT: You MUST return the output in EXACTLY this format:
529 + [INSTAGRAM]
530 + (Your Instagram post content here)
531 + [/INSTAGRAM]
532 + [WHATSAPP]
533 + (Your WhatsApp post content here)
534 + [/WHATSAPP]
535 + [LINKEDIN]
536 + (Your LinkedIn post content here)
537 + [/LINKEDIN]";
538 + } else {
539 + $prompt = "You are a social media expert. Based on the following content, generate two optimized posts for Facebook and Telegram.
540 + Additional Instructions: {$instructions}
541 +
542 + Content:
543 + \"{$source_content}\"
544 +
545 + IMPORTANT: You MUST return the output in EXACTLY this format:
546 + [FACEBOOK]
547 + (Your Facebook post content here)
548 + [/FACEBOOK]
549 + [TELEGRAM]
550 + (Your Telegram post content here)
551 + [/TELEGRAM]";
552 + }
553 +
554 + $ai_response = $this->call_ai( $config_values, $trigger_data, $prompt, "You are a helpful social media manager." );
555 +
556 + if ( ! $ai_response['success'] ) {
557 + return $ai_response;
558 + }
559 +
560 + $content = $ai_response['content'];
561 + $results = array(
562 + 'success' => true,
563 + 'fb_post' => '',
564 + 'telegram_post' => '',
565 + 'insta_post' => '',
566 + 'whatsapp_post' => '',
567 + 'linkedin_post' => '',
568 + 'content' => $content, // Store full response just in case
569 + );
570 +
571 + if ( preg_match( '/\[FACEBOOK\](.*?)\[\/FACEBOOK\]/s', $content, $matches ) ) {
572 + $results['fb_post'] = trim( $matches[1] );
573 + }
574 + if ( preg_match( '/\[TELEGRAM\](.*?)\[\/TELEGRAM\]/s', $content, $matches ) ) {
575 + $results['telegram_post'] = trim( $matches[1] );
576 + }
577 + if ( preg_match( '/\[INSTAGRAM\](.*?)\[\/INSTAGRAM\]/s', $content, $matches ) ) {
578 + $results['insta_post'] = trim( $matches[1] );
579 + }
580 + if ( preg_match( '/\[WHATSAPP\](.*?)\[\/WHATSAPP\]/s', $content, $matches ) ) {
581 + $results['whatsapp_post'] = trim( $matches[1] );
582 + }
583 + if ( preg_match( '/\[LINKEDIN\](.*?)\[\/LINKEDIN\]/s', $content, $matches ) ) {
584 + $results['linkedin_post'] = trim( $matches[1] );
585 + }
586 +
587 + // AI Image Generation
588 + if ( ! empty( $config_values['generate_image'] ) && $config_values['generate_image'] !== "false" ) {
589 + // 1. Generate an image prompt using the text AI
590 + $image_prompt_request = "Based on the following content, generate a concise, high-quality image generation prompt (max 100 words) that would be suitable for a social media post. Focus on visual details.
591 +
592 + Content:
593 + \"{$source_content}\"";
594 +
595 + $image_prompt_response = $this->call_ai( $config_values, $trigger_data, $image_prompt_request, "You are a creative visual designer." );
596 +
597 + if ( $image_prompt_response['success'] ) {
598 + $image_prompt = $image_prompt_response['content'];
599 +
600 + // 2. Call OpenAI DALL-E
601 + $image_result = $this->call_openai_image( $config_values, $trigger_data, $image_prompt );
602 +
603 + if ( $image_result['success'] ) {
604 + $results['generated_image_url'] = $image_result['url'];
605 + } else {
606 + $results['image_error'] = $image_result['message'];
607 + }
608 + }
609 + }
610 +
611 + return $results;
612 + }
613 +
614 + /**
615 + * Call OpenAI Image (DALL-E) API helper
616 + */
617 + private function call_openai_image( $config_values, $trigger_data, $prompt ) {
618 + $api_key = isset( $config_values['api_key'] ) ? $this->parse_tokens( $config_values['api_key'], $trigger_data ) : '';
619 + $model = isset( $config_values['image_model'] ) ? $config_values['image_model'] : 'dall-e-3';
620 + $size = isset( $config_values['image_size'] ) ? $config_values['image_size'] : '1024x1024';
621 + $style = isset( $config_values['image_style'] ) ? $config_values['image_style'] : 'vivid';
622 +
623 + if ( empty( $api_key ) ) {
624 + return array( 'success' => false, 'message' => 'API Key is required for image generation.' );
625 + }
626 +
627 + $url = 'https://api.openai.com/v1/images/generations';
628 +
629 + $body = array(
630 + 'model' => $model,
631 + 'prompt' => $prompt,
632 + 'n' => 1,
633 + 'size' => $size,
634 + );
635 +
636 + if ( $model === 'dall-e-3' ) {
637 + $body['style'] = $style;
638 + }
639 +
640 + $args = array(
641 + 'method' => 'POST',
642 + 'headers' => array(
643 + 'Content-Type' => 'application/json',
644 + 'Authorization' => 'Bearer ' . $api_key,
645 + ),
646 + 'body' => wp_json_encode( $body ),
647 + 'timeout' => 90,
648 + );
649 +
650 + $response = wp_remote_request( $url, $args );
651 +
652 + if ( is_wp_error( $response ) ) {
653 + return array( 'success' => false, 'message' => $response->get_error_message() );
654 + }
655 +
656 + $response_code = wp_remote_retrieve_response_code( $response );
657 + $response_body = wp_remote_retrieve_body( $response );
658 + $data = json_decode( $response_body, true );
659 +
660 + if ( $response_code >= 200 && $response_code < 300 ) {
661 + return array(
662 + 'success' => true,
663 + 'url' => isset( $data['data'][0]['url'] ) ? $data['data'][0]['url'] : '',
664 + 'data' => $data,
665 + );
666 + } else {
667 + $err = isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown OpenAI Image error';
668 + return array( 'success' => false, 'message' => $err, 'code' => $response_code );
669 + }
670 + }
671 +
672 + /**
673 + * Call AI Provider helper
674 + */
675 + private function call_ai( $config_values, $trigger_data, $prompt, $system_prompt = '' ) {
676 + $provider = isset( $config_values['ai_provider'] ) ? $config_values['ai_provider'] : 'openai';
677 +
678 + switch ( $provider ) {
679 + case 'gemini':
680 + return $this->call_gemini( $config_values, $trigger_data, $prompt, $system_prompt );
681 + case 'claude':
682 + return $this->call_claude( $config_values, $trigger_data, $prompt, $system_prompt );
683 + case 'openai':
684 + default:
685 + return $this->call_openai( $config_values, $trigger_data, $prompt, $system_prompt );
686 + }
687 + }
688 +
689 + /**
690 + * Call OpenAI API helper
691 + */
692 + private function call_openai( $config_values, $trigger_data, $prompt, $system_prompt = '' ) {
693 + $api_key = isset( $config_values['api_key'] ) ? $this->parse_tokens( $config_values['api_key'], $trigger_data ) : '';
694 + $model = isset( $config_values['model'] ) ? $config_values['model'] : 'gpt-3.5-turbo';
695 + $max_tokens = isset( $config_values['max_tokens'] ) ? intval( $config_values['max_tokens'] ) : 1500;
696 + $temperature = isset( $config_values['temperature'] ) ? floatval( $config_values['temperature'] ) : 0.7;
697 +
698 + if ( empty( $api_key ) ) {
699 + return array( 'success' => false, 'message' => 'OpenAI API Key is required.' );
700 + }
701 +
702 + $messages = array();
703 + if ( ! empty( $system_prompt ) ) {
704 + $messages[] = array( 'role' => 'system', 'content' => $system_prompt );
705 + }
706 + $messages[] = array( 'role' => 'user', 'content' => $prompt );
707 +
708 + $url = 'https://api.openai.com/v1/chat/completions';
709 + $args = array(
710 + 'method' => 'POST',
711 + 'headers' => array(
712 + 'Content-Type' => 'application/json',
713 + 'Authorization' => 'Bearer ' . $api_key,
714 + ),
715 + 'body' => wp_json_encode( array(
716 + 'model' => $model,
717 + 'messages' => $messages,
718 + 'max_tokens' => $max_tokens,
719 + 'temperature' => $temperature,
720 + ) ),
721 + 'timeout' => 60,
722 + );
723 +
724 + $response = wp_remote_request( $url, $args );
725 +
726 + if ( is_wp_error( $response ) ) {
727 + return array( 'success' => false, 'message' => $response->get_error_message() );
728 + }
729 +
730 + $response_code = wp_remote_retrieve_response_code( $response );
731 + $response_body = wp_remote_retrieve_body( $response );
732 + $data = json_decode( $response_body, true );
733 +
734 + if ( $response_code >= 200 && $response_code < 300 ) {
735 + return array(
736 + 'success' => true,
737 + 'content' => isset( $data['choices'][0]['message']['content'] ) ? $data['choices'][0]['message']['content'] : '',
738 + 'data' => $data,
739 + );
740 + } else {
741 + $err = isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown OpenAI error';
742 + return array( 'success' => false, 'message' => $err, 'code' => $response_code );
743 + }
744 + }
745 +
746 + /**
747 + * Call Gemini API helper
748 + */
749 + private function call_gemini( $config_values, $trigger_data, $prompt, $system_prompt = '' ) {
750 + $api_key = isset( $config_values['api_key'] ) ? $this->parse_tokens( $config_values['api_key'], $trigger_data ) : '';
751 + $model = isset( $config_values['model'] ) ? $config_values['model'] : 'gemini-1.5-flash';
752 + $temperature = isset( $config_values['temperature'] ) ? floatval( $config_values['temperature'] ) : 0.7;
753 +
754 + if ( empty( $api_key ) ) {
755 + return array( 'success' => false, 'message' => 'Gemini API Key is required.' );
756 + }
757 +
758 + $full_prompt = $system_prompt ? $system_prompt . "\n\n" . $prompt : $prompt;
759 +
760 + $url = 'https://generativelanguage.googleapis.com/v1/models/' . $model . ':generateContent?key=' . $api_key;
761 +
762 + $body = array(
763 + 'contents' => array(
764 + array(
765 + 'parts' => array(
766 + array( 'text' => $full_prompt ),
767 + ),
768 + ),
769 + ),
770 + 'generationConfig' => array(
771 + 'temperature' => $temperature,
772 + ),
773 + );
774 +
775 + $args = array(
776 + 'method' => 'POST',
777 + 'headers' => array( 'Content-Type' => 'application/json' ),
778 + 'body' => wp_json_encode( $body ),
779 + 'timeout' => 60,
780 + );
781 +
782 + $response = wp_remote_request( $url, $args );
783 +
784 + if ( is_wp_error( $response ) ) {
785 + return array( 'success' => false, 'message' => $response->get_error_message() );
786 + }
787 +
788 + $response_code = wp_remote_retrieve_response_code( $response );
789 + $response_body = wp_remote_retrieve_body( $response );
790 + $data = json_decode( $response_body, true );
791 +
792 + if ( $response_code >= 200 && $response_code < 300 ) {
793 + $content = isset( $data['candidates'][0]['content']['parts'][0]['text'] ) ? $data['candidates'][0]['content']['parts'][0]['text'] : '';
794 + return array(
795 + 'success' => true,
796 + 'content' => $content,
797 + 'data' => $data,
798 + );
799 + } else {
800 + $err = isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown Gemini error';
801 + return array( 'success' => false, 'message' => $err, 'code' => $response_code );
802 + }
803 + }
804 +
805 + /**
806 + * Call Claude API helper
807 + */
808 + private function call_claude( $config_values, $trigger_data, $prompt, $system_prompt = '' ) {
809 + $api_key = isset( $config_values['api_key'] ) ? $this->parse_tokens( $config_values['api_key'], $trigger_data ) : '';
810 + $model = isset( $config_values['model'] ) ? $config_values['model'] : 'claude-3-5-sonnet-20240620';
811 + $max_tokens = isset( $config_values['max_tokens'] ) ? intval( $config_values['max_tokens'] ) : 1500;
812 + $temperature = isset( $config_values['temperature'] ) ? floatval( $config_values['temperature'] ) : 0.7;
813 +
814 + if ( empty( $api_key ) ) {
815 + return array( 'success' => false, 'message' => 'Claude API Key is required.' );
816 + }
817 +
818 + $url = 'https://api.anthropic.com/v1/messages';
819 +
820 + $body = array(
821 + 'model' => $model,
822 + 'messages' => array(
823 + array( 'role' => 'user', 'content' => $prompt ),
824 + ),
825 + 'max_tokens' => $max_tokens,
826 + 'temperature' => $temperature,
827 + );
828 +
829 + if ( ! empty( $system_prompt ) ) {
830 + $body['system'] = $system_prompt;
831 + }
832 +
833 + $args = array(
834 + 'method' => 'POST',
835 + 'headers' => array(
836 + 'Content-Type' => 'application/json',
837 + 'x-api-key' => $api_key,
838 + 'anthropic-version' => '2023-06-01',
839 + ),
840 + 'body' => wp_json_encode( $body ),
841 + 'timeout' => 60,
842 + );
843 +
844 + $response = wp_remote_request( $url, $args );
845 +
846 + if ( is_wp_error( $response ) ) {
847 + return array( 'success' => false, 'message' => $response->get_error_message() );
848 + }
849 +
850 + $response_code = wp_remote_retrieve_response_code( $response );
851 + $response_body = wp_remote_retrieve_body( $response );
852 + $data = json_decode( $response_body, true );
853 +
854 + if ( $response_code >= 200 && $response_code < 300 ) {
855 + $content = isset( $data['content'][0]['text'] ) ? $data['content'][0]['text'] : '';
856 + return array(
857 + 'success' => true,
858 + 'content' => $content,
859 + 'data' => $data,
860 + );
861 + } else {
862 + $err = isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown Claude error';
863 + return array( 'success' => false, 'message' => $err, 'code' => $response_code );
864 + }
865 + }
866 +}