PluginProbe ʕ •ᴥ•ʔ
OttoKit: All-in-One Automation Platform / 1.1.32
OttoKit: All-in-One Automation Platform v1.1.32
1.1.32 1.1.31 1.1.30 1.1.29 1.1.28 1.1.27 1.1.9 trunk 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 1.0.39 1.0.40 1.0.41 1.0.42 1.0.43 1.0.44 1.0.45 1.0.46 1.0.47 1.0.48 1.0.49 1.0.50 1.0.51 1.0.52 1.0.53 1.0.54 1.0.55 1.0.56 1.0.57 1.0.58 1.0.59 1.0.60 1.0.61 1.0.62 1.0.63 1.0.64 1.0.65 1.0.66 1.0.67 1.0.68 1.0.69 1.0.7 1.0.70 1.0.71 1.0.72 1.0.73 1.0.74 1.0.75 1.0.76 1.0.77 1.0.78 1.0.79 1.0.8 1.0.80 1.0.81 1.0.82 1.0.83 1.0.84 1.0.85 1.0.86 1.0.87 1.0.88 1.0.89 1.0.9 1.0.90 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.20 1.1.21 1.1.22 1.1.23 1.1.24 1.1.25 1.1.26 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8
suretriggers / src / Integrations / fluentcommunity / actions / reply-to-post.php
suretriggers / src / Integrations / fluentcommunity / actions Last commit date
add-user-to-course.php 1 year ago add-user-to-space.php 1 year ago create-lesson-in-course.php 9 months ago create-new-post-feed.php 1 year ago fetch-members.php 6 months ago get-all-courses-list.php 1 year ago get-all-spaces-list.php 1 year ago like-post.php 6 months ago list-course-sections.php 9 months ago list-posts-in-space.php 6 months ago remove-user-from-course.php 1 year ago remove-user-from-space.php 1 year ago reply-to-post.php 7 months ago
reply-to-post.php
479 lines
1 <?php
2 /**
3 * ReplyToPost.
4 * php version 5.6
5 *
6 * @category ReplyToPost
7 * @package SureTriggers
8 * @author BSF <username@example.com>
9 * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
10 * @link https://www.brainstormforce.com/
11 * @since 1.0.0
12 */
13
14 namespace SureTriggers\Integrations\FluentCommunity\Actions;
15
16 use Exception;
17 use SureTriggers\Integrations\AutomateAction;
18 use SureTriggers\Traits\SingletonLoader;
19 use FluentCommunity\App\Models\Feed;
20 use FluentCommunity\App\Models\Comment;
21 use FluentCommunity\App\Models\Media;
22 use FluentCommunity\App\Services\Helper;
23 use FluentCommunity\Framework\Support\Arr;
24
25 /**
26 * ReplyToPost
27 *
28 * @category ReplyToPost
29 * @package SureTriggers
30 */
31 class ReplyToPost extends AutomateAction {
32
33 /**
34 * Integration type.
35 *
36 * @var string
37 */
38 public $integration = 'FluentCommunity';
39
40 /**
41 * Action name.
42 *
43 * @var string
44 */
45 public $action = 'fc_reply_to_post';
46
47 use SingletonLoader;
48
49 /**
50 * Register an action.
51 *
52 * @param array $actions Actions array.
53 *
54 * @return array
55 */
56 public function register( $actions ) {
57 $actions[ $this->integration ][ $this->action ] = [
58 'label' => __( 'Reply to Post', 'suretriggers' ),
59 'action' => $this->action,
60 'function' => [ $this, 'action_listener' ],
61 ];
62
63 return $actions;
64 }
65
66 /**
67 * Action listener.
68 *
69 * @param int $user_id User ID.
70 * @param int $automation_id Automation ID.
71 * @param array $fields Fields.
72 * @param array $selected_options Selected options.
73 *
74 * @return array|void
75 *
76 * @throws Exception Exception.
77 */
78 public function _action_listener( $user_id, $automation_id, $fields, $selected_options ) {
79 $post_id = isset( $selected_options['post_id'] ) ? (int) sanitize_text_field( $selected_options['post_id'] ) : 0;
80 $user_email = isset( $selected_options['user_email'] ) ? sanitize_email( $selected_options['user_email'] ) : '';
81 $reply_content = isset( $selected_options['reply_content'] ) ? sanitize_textarea_field( $selected_options['reply_content'] ) : '';
82 $media_images = isset( $selected_options['media_images'] ) ? $this->parse_media_input( $selected_options['media_images'] ) : [];
83 // Validate user ID.
84 $user = get_user_by( 'email', $user_email );
85
86 if ( ! $user ) {
87 return [
88 'status' => 'error',
89 'message' => 'User not found with the provided email.',
90 ];
91 }
92
93 // Validate post ID and get the post.
94 if ( ! $post_id ) {
95 return [
96 'status' => 'error',
97 'message' => 'Post ID is required.',
98 ];
99 }
100
101 if ( empty( $reply_content ) ) {
102 return [
103 'status' => 'error',
104 'message' => 'Reply content is required.',
105 ];
106 }
107
108 // Check if FluentCommunity classes exist.
109 if ( ! class_exists( '\FluentCommunity\App\Models\Feed' ) || ! class_exists( '\FluentCommunity\App\Models\Comment' ) ) {
110 return [
111 'status' => 'error',
112 'message' => 'FluentCommunity is not available.',
113 ];
114 }
115
116 // Get the post.
117 $post = Feed::find( $post_id );
118
119 if ( ! $post ) {
120 return [
121 'status' => 'error',
122 'message' => 'The specified post does not exist.',
123 ];
124 }
125
126 // Check if user has permission to comment on this post.
127 if ( $post->space_id ) {
128 // Check if user has access to the space.
129 $space_ids = get_user_meta( $user->ID, '_fcom_space_ids', true );
130 if ( ! $space_ids || ! is_array( $space_ids ) || ! in_array( $post->space_id, $space_ids ) ) {
131 $space = $post->space;
132 if ( ! $space || 'public' !== $space->privacy ) {
133 return [
134 'status' => 'error',
135 'message' => 'User does not have permission to reply to this post.',
136 ];
137 }
138 }
139 }
140
141 // Process media attachments.
142 $media_items = $this->process_media_attachments( $media_images, $user->ID );
143
144 // Create the comment/reply.
145 $comment_data = [
146 'user_id' => $user->ID,
147 'post_id' => $post_id,
148 'parent_id' => null,
149 'message' => $reply_content,
150 'message_rendered' => wp_kses_post( $reply_content ),
151 'type' => 'comment',
152 'status' => 'published',
153 'meta' => [],
154 ];
155
156 // Add media items to meta if available.
157 if ( ! empty( $media_items ) ) {
158 $comment_data['meta']['media_items'] = $media_items;
159 }
160
161 try {
162 $comment = Comment::create( $comment_data );
163
164 if ( ! $comment ) {
165 return [
166 'status' => 'error',
167 'message' => 'Failed to create reply.',
168 ];
169 }
170
171 // Associate media with comment if available.
172 if ( ! empty( $media_items ) ) {
173 $this->associate_media_with_comment( $media_items, $comment->id, $post_id );
174 }
175
176 // Update post comment count.
177 $post->increment( 'comments_count' );
178
179 // Fire action hook for other integrations.
180 do_action( 'fluent_community_comment_created', $comment, $post );
181
182 return [
183 'status' => 'success',
184 'response' => 'Reply created successfully',
185 'comment_id' => $comment->id,
186 'post_id' => $post_id,
187 'user_id' => $user->ID,
188 'reply_content' => $reply_content,
189 'comment_url' => $post->getPermalink() . '?comment_id=' . $comment->id,
190 'media_count' => count( $media_items ),
191 ];
192
193 } catch ( Exception $e ) {
194 return [
195 'status' => 'error',
196 'message' => 'Failed to create reply: ' . $e->getMessage(),
197 ];
198 }
199 }
200
201 /**
202 * Parse media input from comma separated string or array.
203 *
204 * @param string|array $input Comma separated URLs or array.
205 *
206 * @return array Array of media URLs.
207 */
208 private function parse_media_input( $input ) {
209 // If already an array, return as is.
210 if ( is_array( $input ) ) {
211 return $input;
212 }
213
214 // If string, split by comma and clean up.
215 if ( is_string( $input ) && ! empty( $input ) ) {
216 $urls = explode( ',', $input );
217 $cleaned_urls = [];
218
219 foreach ( $urls as $url ) {
220 $url = trim( $url );
221 if ( ! empty( $url ) ) {
222 // Basic URL validation.
223 if ( filter_var( $url, FILTER_VALIDATE_URL ) ) {
224 $cleaned_urls[] = $url;
225 }
226 }
227 }
228
229 return $cleaned_urls;
230 }
231
232 return [];
233 }
234
235 /**
236 * Process media attachments from URLs or file uploads.
237 *
238 * @param array $media_images Array of media URLs or file paths.
239 * @param int $user_id User ID.
240 *
241 * @return array Array of processed media items.
242 */
243 private function process_media_attachments( $media_images, $user_id ) {
244 $media_items = [];
245
246 if ( empty( $media_images ) || ! is_array( $media_images ) ) {
247 return $media_items;
248 }
249
250 // Check if FluentCommunity classes are available.
251 if ( ! class_exists( '\FluentCommunity\App\Models\Media' ) || ! class_exists( '\FluentCommunity\Framework\Support\Arr' ) ) {
252 return $media_items;
253 }
254
255 foreach ( $media_images as $media_image ) {
256 if ( empty( $media_image ) ) {
257 continue;
258 }
259
260 // If it's a URL string, convert to array format.
261 if ( is_string( $media_image ) ) {
262 $media_image = [ 'url' => $media_image ];
263 }
264
265 try {
266 // Download and create media from URL.
267 $media = $this->create_media_from_url( $media_image, $user_id );
268
269 if ( $media && isset( $media->id ) ) {
270 $settings = isset( $media->settings ) ? $media->settings : [];
271 $media_items[] = [
272 'media_id' => $media->id,
273 'url' => isset( $media->public_url ) ? $media->public_url : '',
274 'type' => isset( $media->media_type ) ? $media->media_type : 'image',
275 'width' => Arr::get( $settings, 'width' ),
276 'height' => Arr::get( $settings, 'height' ),
277 'provider' => Arr::get( $settings, 'provider', 'uploader' ),
278 ];
279 }
280 } catch ( Exception $e ) {
281 // Continue processing other media on error.
282 unset( $e );
283 }
284 }
285
286 return $media_items;
287 }
288
289 /**
290 * Associate processed media with the comment.
291 *
292 * @param array $media_items Array of processed media items.
293 * @param int $comment_id Comment ID.
294 * @param int $feed_id Feed/Post ID.
295 * @return void
296 */
297 private function associate_media_with_comment( $media_items, $comment_id, $feed_id ) {
298 // Check if FluentCommunity classes are available.
299 if ( ! class_exists( '\FluentCommunity\App\Models\Media' ) || ! class_exists( '\FluentCommunity\Framework\Support\Arr' ) ) {
300 return;
301 }
302
303 foreach ( $media_items as $media_item ) {
304 $media_id = Arr::get( $media_item, 'media_id' );
305
306 if ( $media_id ) {
307 try {
308 $media = Media::find( $media_id );
309
310 if ( $media ) {
311 $media->fill(
312 [
313 'is_active' => 1,
314 'feed_id' => $feed_id,
315 'object_source' => 'comment',
316 'sub_object_id' => $comment_id,
317 ]
318 );
319 $media->save();
320 }
321 } catch ( Exception $e ) {
322 // Continue with other media items on error.
323 unset( $e );
324 }
325 }
326 }
327 }
328
329 /**
330 * Create media from URL by downloading and uploading to FluentCommunity.
331 *
332 * @param string|array $media_data URL or array with URL.
333 * @param int $user_id User ID.
334 *
335 * @return object|null Media object if successful, null otherwise.
336 */
337 private function create_media_from_url( $media_data, $user_id ) {
338 // Check if FluentCommunity classes are available.
339 if ( ! class_exists( '\FluentCommunity\App\Models\Media' ) || ! class_exists( '\FluentCommunity\Framework\Support\Arr' ) ) {
340 return null;
341 }
342
343 $url = is_array( $media_data ) ? Arr::get( $media_data, 'url' ) : $media_data;
344
345 if ( empty( $url ) ) {
346 return null;
347 }
348
349 // Validate URL.
350 if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
351 return null;
352 }
353
354 // Check if media already exists for this URL.
355 $existing_media = Media::where( 'settings', 'LIKE', '%' . $url . '%' )
356 ->where( 'is_active', 1 )
357 ->first();
358
359 if ( $existing_media ) {
360 return $existing_media;
361 }
362
363 try {
364 // Download the file.
365 $response = wp_remote_get(
366 $url,
367 [
368 'headers' => [
369 'User-Agent' => 'FluentCommunity/1.0',
370 ],
371 ]
372 );
373
374 if ( is_wp_error( $response ) ) {
375 return null;
376 }
377
378 $body = wp_remote_retrieve_body( $response );
379 $content_type = wp_remote_retrieve_header( $response, 'content-type' );
380
381 if ( empty( $body ) ) {
382 return null;
383 }
384
385 // Get file info.
386 $url_path = wp_parse_url( $url, PHP_URL_PATH );
387 $file_name = $url_path ? basename( $url_path ) : 'unknown_file';
388 if ( empty( $file_name ) || strpos( $file_name, '.' ) === false ) {
389 // Generate filename based on content type.
390 $content_type_string = is_array( $content_type ) ? '' : $content_type;
391 $ext = $this->get_extension_from_content_type( $content_type_string );
392 $file_name = 'media_' . time() . '.' . $ext;
393 }
394
395 // Prepare file name for WordPress upload.
396 $file_name = sanitize_file_name( $file_name );
397
398 // Save file.
399 $upload = wp_upload_bits( $file_name, null, $body );
400 if ( $upload['error'] ) {
401 return null;
402 }
403 $file_path = $upload['file'];
404
405 // Get image file info.
406 $file_info = getimagesize( $file_path );
407 $media_type = 'image';
408 $settings = [
409 'original_name' => $file_name,
410 'source_url' => $url,
411 'provider' => 'external_download',
412 ];
413
414 if ( $file_info ) {
415 $settings['width'] = $file_info[0];
416 $settings['height'] = $file_info[1];
417 } else {
418 // Check if it's a document.
419 $allowed_types = [ 'pdf', 'doc', 'docx', 'xls', 'xlsx' ];
420 $ext = pathinfo( $file_name, PATHINFO_EXTENSION );
421 if ( in_array( strtolower( $ext ), $allowed_types ) ) {
422 $media_type = 'document';
423 }
424 }
425
426 // Create media record.
427 $media_data = [
428 'user_id' => $user_id,
429 'media_key' => md5( $url . '_' . time() ),
430 'media_type' => $media_type,
431 'driver' => 'local',
432 'media_path' => $file_path,
433 'media_url' => $upload['url'],
434 'settings' => $settings,
435 'object_source' => 'temp',
436 'is_active' => 0, // Will be activated when associated with comment.
437 ];
438
439 $media = Media::create( $media_data );
440
441 if ( $media ) {
442 return $media;
443 }
444 } catch ( Exception $e ) {
445 // Clean up file if it was created.
446 if ( isset( $file_path ) && file_exists( $file_path ) ) {
447 wp_delete_file( $file_path );
448 }
449 }
450
451 return null;
452 }
453
454 /**
455 * Get file extension from content type.
456 *
457 * @param string $content_type Content type.
458 *
459 * @return string File extension.
460 */
461 private function get_extension_from_content_type( $content_type ) {
462 $types = [
463 'image/jpeg' => 'jpg',
464 'image/jpg' => 'jpg',
465 'image/png' => 'png',
466 'image/gif' => 'gif',
467 'image/webp' => 'webp',
468 'application/pdf' => 'pdf',
469 'text/plain' => 'txt',
470 'application/msword' => 'doc',
471 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
472 ];
473
474 return isset( $types[ $content_type ] ) ? $types[ $content_type ] : 'jpg';
475 }
476 }
477
478 ReplyToPost::get_instance();
479