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 |