PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.4.4
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.4.4
3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.10 All 111 releases
templately / includes / Core / Importer / Utils / FluentImport.php

FluentImport.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.4.4, at includes/Core/Importer/Utils/FluentImport.php

545 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Fluent Cart Product Importer
4 *
5 * Imports Fluent Cart products from JSON format with images
6 * Replicates the functionality of Fluent Cart's DummyProductService
7 *
8 * @package Templately\Exporter\Core
9 * @since 1.3.4
10 */
11
12 namespace Templately\Core\Importer\Utils;
13
14 use Exception;
15 use FluentCart\App\Models\Product;
16 use FluentCart\App\Services\Async\ImageAttachService;
17 use FluentCart\App\Services\DateTime\DateTime;
18 use FluentCart\Framework\Support\Arr;
19 use FluentCart\Framework\Support\Str;
20
21 /**
22 * FluentImport Class
23 *
24 * Handles importing Fluent Cart products with their images from JSON format
25 * Uses the same approach as Fluent Cart's DummyProductService
26 *
27 * @since 1.3.4
28 */
29 class FluentImport {
30
31 /**
32 * Import file path
33 *
34 * @var string
35 * @since 1.3.4
36 */
37 private $import_path;
38
39 /**
40 * Images directory path
41 *
42 * @var string
43 * @since 1.3.4
44 */
45 private $images_dir;
46
47 /**
48 * Import statistics
49 *
50 * @var array
51 * @since 1.3.4
52 */
53 private $stats = [
54 'products_imported' => 0,
55 'products_failed' => 0,
56 'variations_imported' => 0,
57 'images_imported' => 0,
58 ];
59
60 /**
61 * Product ID mappings (original ID => new ID)
62 *
63 * @var array
64 * @since 1.3.4
65 */
66 private $product_id_mappings = [
67 'succeed' => [],
68 'failed' => [],
69 ];
70
71 /**
72 * Import errors
73 *
74 * @var array
75 * @since 1.3.4
76 */
77 private $errors = [];
78
79 /**
80 * Products array for action hooks
81 *
82 * @var array
83 * @since 1.3.4
84 */
85 public $products = [];
86
87 /**
88 * Constructor
89 *
90 * @param string $import_path Path to the JSON file to import
91 * @since 1.3.4
92 */
93 public function __construct( $import_path ) {
94 $this->import_path = $import_path;
95 $this->images_dir = dirname( $import_path ) . '/images';
96 $this->require_files();
97 }
98
99 /**
100 * Require necessary WordPress files
101 *
102 * @return void
103 * @since 1.3.4
104 */
105 private function require_files() {
106 if ( ! function_exists( 'media_handle_upload' ) ) {
107 require_once ABSPATH . 'wp-admin/includes/image.php';
108 require_once ABSPATH . 'wp-admin/includes/file.php';
109 require_once ABSPATH . 'wp-admin/includes/media.php';
110 }
111 if ( ! function_exists( 'wp_create_term' ) ) {
112 require_once ABSPATH . 'wp-admin/includes/taxonomy.php';
113 }
114 }
115
116 /**
117 * Import products from JSON
118 *
119 * @return array Structured import result with status, errors, and summary
120 * @since 1.3.4
121 */
122 public function import() {
123 try {
124 // Check if file exists
125 if ( ! file_exists( $this->import_path ) ) {
126 throw new Exception( 'Import file not found: ' . $this->import_path );
127 }
128
129 // Check if images directory exists
130 if ( ! file_exists( $this->images_dir ) ) {
131 throw new Exception( 'Images directory not found: ' . $this->images_dir );
132 }
133
134 // Load JSON
135 $json = file_get_contents( $this->import_path );
136 if ( $json === false ) {
137 throw new Exception( 'Failed to read JSON file' );
138 }
139
140 $products = json_decode( $json, true );
141 if ( json_last_error() !== JSON_ERROR_NONE ) {
142 throw new Exception( 'Failed to parse JSON: ' . json_last_error_msg() );
143 }
144
145 if ( empty( $products ) || ! is_array( $products ) ) {
146 return $this->format_result( 'success', [], [] );
147 }
148
149 // Store products for action hooks (similar to WPImport::$posts)
150 $this->products = $this->prepare_products_for_hooks( $products );
151
152 // Trigger Templately-specific import_start action
153 do_action( 'templately_import_start', $this );
154
155 // Import each product
156 foreach ( $products as $product_data ) {
157 // Prepare result array for this product
158 $result = [
159 'succeed' => $this->product_id_mappings['succeed'],
160 'failed' => $this->product_id_mappings['failed'],
161 ];
162
163 try {
164 $product_id = $this->insert_product( $product_data );
165 $this->stats['products_imported']++;
166
167 // Track product ID mapping if original ID exists
168 if ( isset( $product_data['id'] ) ) {
169 $this->product_id_mappings['succeed'][ $product_data['id'] ] = $product_id;
170 }
171
172 // Update result with new mapping
173 $result['succeed'] = $this->product_id_mappings['succeed'];
174
175 // Prepare post-like data for action hook
176 $post_data = $this->prepare_post_data_for_hook( $product_data, $product_id );
177
178 // Trigger process_post action (similar to WPImport)
179 do_action( 'templately_import.process_post', $post_data, $result, $this );
180
181 } catch ( Exception $e ) {
182 $this->stats['products_failed']++;
183 $error_msg = 'FluentImport: Failed to import product: ' . $e->getMessage();
184 $this->errors[] = $error_msg;
185 error_log( $error_msg );
186
187 // Track failed product id if original id exists
188 if ( isset( $product_data['id'] ) ) {
189 $this->product_id_mappings['failed'][] = $product_data['id'];
190 }
191
192 // Update result with failed ID
193 $result['failed'] = $this->product_id_mappings['failed'];
194
195 // Prepare post-like data for action hook (even for failed imports)
196 $post_data = $this->prepare_post_data_for_hook( $product_data, null );
197
198 // Trigger process_post action for failed import
199 do_action( 'templately_import.process_post', $post_data, $result, $this );
200 }
201 }
202
203 return $this->format_result( 'success', $this->errors, $this->product_id_mappings );
204
205 } catch ( Exception $e ) {
206 $error_msg = 'Import failed: ' . $e->getMessage();
207 return $this->format_result( 'error', [ $error_msg ], [] );
208 }
209 }
210
211 /**
212 * Format import result in structured JSON format
213 *
214 * @param string $status Status of import (success or error)
215 * @param array $errors Array of error messages
216 * @param array $product_mappings Product ID mappings
217 * @return array Formatted result
218 * @since 1.3.4
219 */
220 private function format_result( $status, $errors, $product_mappings ) {
221 return [
222 'status' => $status,
223 'errors' => $errors,
224 'summary' => [
225 'terms' => [
226 'succeed' => [],
227 'failed' => [],
228 ],
229 'posts' => [
230 'succeed' => $product_mappings['succeed'] ?? [],
231 'failed' => $product_mappings['failed'] ?? [],
232 ],
233 ],
234 ];
235 }
236
237 /**
238 * Prepare products array for action hooks
239 * Converts products to a format similar to WPImport::$posts
240 *
241 * @param array $products Products array from JSON
242 * @return array Prepared products array
243 * @since 1.3.4
244 */
245 private function prepare_products_for_hooks( $products ) {
246 $prepared = [];
247 foreach ( $products as $product ) {
248 $prepared[] = [
249 'post_id' => $product['id'] ?? 0,
250 'post_type' => 'fluent-products',
251 'post_title' => $product['post_title'] ?? '',
252 ];
253 }
254 return $prepared;
255 }
256
257 /**
258 * Prepare post-like data for action hook
259 * Converts product data to a format similar to WPImport post data
260 *
261 * @param array $product_data Product data from JSON
262 * @param int|null $product_id New product ID (null if failed)
263 * @return array Post-like data array
264 * @since 1.3.4
265 */
266 private function prepare_post_data_for_hook( $product_data, $product_id ) {
267 return [
268 'post_id' => $product_data['id'] ?? 0,
269 'post_type' => 'fluent-products',
270 'post_title' => $product_data['post_title'] ?? '',
271 'new_id' => $product_id,
272 ];
273 }
274
275 /**
276 * Insert a single product
277 * Replicates DummyProductService::insert() method
278 *
279 * @param array $product Product data array
280 * @return int The ID of the created product
281 * @throws Exception If product insertion fails
282 * @since 1.3.4
283 */
284 private function insert_product( $product ) {
285 $product_name = Str::slug( $product['post_title'], '-', null );
286 $now = DateTime::gmtNow();
287 $created_date = $now->format( 'Y-m-d H:i:s' );
288 $product_name_suffix = $now->format( 'd-m-Y-H-i-s' );
289
290 $data = [
291 'post_author' => get_current_user_id(),
292 'post_date' => $created_date,
293 'post_date_gmt' => $created_date,
294 'post_content_filtered' => '',
295 'post_status' => 'publish',
296 'post_type' => 'fluent-products',
297 'comment_status' => 'open',
298 'ping_status' => 'closed',
299 'post_password' => '',
300 'post_name' => $product_name . '-' . $product_name_suffix,
301 'to_ping' => '',
302 'pinged' => '',
303 'post_modified' => $created_date,
304 'post_modified_gmt' => $created_date,
305 'post_parent' => 0,
306 'menu_order' => 0,
307 'post_mime_type' => '',
308 'guid' => get_site_url() . '/?items=' . $product_name . '-' . $product_name_suffix,
309 ];
310
311 $product = array_merge( $product, $data );
312 $detail = Arr::get( $product, 'detail', [] );
313 $variant_data = Arr::get( $product, 'variants', [] );
314 $gallery_images = Arr::get( $product, 'gallery', [] );
315 $categories = Arr::get( $product, 'categories', [] );
316
317 // Create product using Fluent Cart's ORM
318 $product_model = Product::query()->create(
319 Arr::except( $product, [ 'detail', 'variants', 'gallery', 'categories' ] )
320 );
321
322 if ( ! $product_model ) {
323 throw new Exception( 'Failed to create product' );
324 }
325
326 // Attach categories
327 $this->attach_terms( $categories, $product_model->ID );
328
329 // Attach gallery images
330 if ( ! empty( $gallery_images ) ) {
331 $this->attach_images_to_product( $product_model->toArray(), $gallery_images );
332
333 // Set featured image from first gallery image
334 $gallery_image_with_id = get_post_meta( $product_model->ID, 'fluent-products-gallery-image', true );
335 if ( isset( $gallery_image_with_id[0] ) ) {
336 set_post_thumbnail( $product_model->ID, Arr::get( $gallery_image_with_id, '0.id' ) );
337 }
338 }
339
340 // Create variants
341 if ( ! empty( $variant_data ) ) {
342 $variants = $product_model->variants()->createMany( $variant_data );
343 $this->stats['variations_imported'] += $variants->count();
344
345 // Attach images to variants
346 foreach ( $variants as $index => $variant ) {
347 $images = Arr::get( $variant_data, $index . '.images', [] );
348 if ( is_array( $images ) && count( $images ) ) {
349 $this->attach_images_to_variant( $variant->id, $images );
350 }
351 }
352
353 // Create product detail with default variation
354 if ( ! empty( $detail ) ) {
355 $detail['post_id'] = $product_model->ID;
356 $detail['default_variation_id'] = $variants->first()->id;
357 $product_model->detail()->create( $detail );
358 }
359 } else {
360 // Create product detail without default variation
361 if ( ! empty( $detail ) ) {
362 $detail['post_id'] = $product_model->ID;
363 $product_model->detail()->create( $detail );
364 }
365 }
366
367 return $product_model->ID;
368 }
369
370 /**
371 * Attach terms to product
372 * Replicates DummyProductService::attachTerms() method
373 *
374 * @param array|string $categories Categories to attach
375 * @param int $post_id Post ID
376 * @return void
377 * @since 1.3.4
378 */
379 private function attach_terms( $categories, $post_id ) {
380 if ( is_string( $categories ) ) {
381 $categories = explode( ',', $categories );
382 }
383
384 $term_ids = [];
385
386 if ( is_array( $categories ) ) {
387 foreach ( $categories as $category ) {
388 $term = wp_create_term( $category, 'product-categories' );
389 if ( ! is_wp_error( $term ) ) {
390 $term_ids[] = $term['term_id'];
391 }
392 }
393 }
394
395 if ( ! empty( $term_ids ) ) {
396 wp_set_post_terms( $post_id, $term_ids, 'product-categories' );
397 }
398 }
399
400 /**
401 * Attach images to product
402 * Uses ImageAttachService similar to DummyProductService
403 *
404 * @param array $product Product array
405 * @param array $images Array of image paths
406 * @return void
407 * @since 1.3.4
408 */
409 private function attach_images_to_product( array $product, array $images ) {
410 if ( empty( $images ) ) {
411 return;
412 }
413
414 $gallery = [];
415
416 foreach ( $images as $image_path ) {
417 $value = $this->add_image_from_path( $image_path, $product['post_title'] );
418 if ( ! empty( $value ) ) {
419 $gallery[] = $value;
420 $this->stats['images_imported']++;
421 }
422 }
423
424 if ( ! empty( $gallery ) ) {
425 update_post_meta( $product['ID'], 'fluent-products-gallery-image', $gallery );
426 }
427 }
428
429 /**
430 * Attach images to variant
431 * Uses ImageAttachService similar to DummyProductService
432 *
433 * @param int $variant_id Variant ID
434 * @param array $images Array of image paths
435 * @return void
436 * @since 1.3.4
437 */
438 private function attach_images_to_variant( $variant_id, array $images ) {
439 if ( empty( $images ) ) {
440 return;
441 }
442
443 // Use ProductVariation model to access media() relationship
444 $variant = \FluentCart\App\Models\ProductVariation::query()->find( $variant_id );
445 if ( empty( $variant ) ) {
446 return;
447 }
448
449 $meta_value = [];
450 foreach ( $images as $image_path ) {
451 $value = $this->add_image_from_path( $image_path, $variant['variation_title'] );
452 if ( ! empty( $value ) ) {
453 $meta_value[] = $value;
454 $this->stats['images_imported']++;
455 }
456 }
457
458 if ( ! empty( $meta_value ) ) {
459 // Use media() relationship like ImageAttachService does
460 // meta_value should be an array, not serialized
461 $media = [
462 'meta_value' => $meta_value,
463 'object_id' => $variant['id'],
464 'object_type' => 'product_variant_info',
465 'meta_key' => 'product_thumbnail',
466 ];
467 $variant->media()->create( $media );
468 }
469 }
470
471 /**
472 * Add image from local path
473 * Similar to ImageAttachService::addImageFromUrl() but for local files
474 *
475 * @param string $image_path Relative image path (e.g., 'images/photo.jpg')
476 * @param string $title Image title
477 * @return array Image data array with id, title, and url
478 * @since 1.3.4
479 */
480 private function add_image_from_path( $image_path, $title ) {
481 // Convert relative path to absolute
482 $full_path = dirname( $this->import_path ) . '/' . $image_path;
483
484 if ( ! file_exists( $full_path ) ) {
485 error_log( 'FluentImport: Image file not found: ' . $full_path );
486 return [];
487 }
488
489 // Check if image already exists in media library
490 $filename = basename( $full_path );
491 global $wpdb;
492 $existing = $wpdb->get_var(
493 $wpdb->prepare(
494 "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND guid LIKE %s",
495 '%' . $wpdb->esc_like( $filename )
496 )
497 );
498
499 if ( $existing ) {
500 $media = wp_prepare_attachment_for_js( $existing );
501 return [
502 'id' => $existing,
503 'title' => $media['title'],
504 'url' => $media['url'],
505 ];
506 }
507
508 // Upload image to WordPress media library
509 $upload_dir = wp_upload_dir();
510 $dest_file = $upload_dir['path'] . '/' . $filename;
511
512 // Copy file to uploads directory
513 if ( ! copy( $full_path, $dest_file ) ) {
514 error_log( 'FluentImport: Failed to copy image: ' . $full_path );
515 return [];
516 }
517
518 // Create attachment
519 $attachment_data = [
520 'post_mime_type' => mime_content_type( $dest_file ),
521 'post_title' => sanitize_file_name( pathinfo( $filename, PATHINFO_FILENAME ) ),
522 'post_content' => '',
523 'post_status' => 'inherit',
524 ];
525
526 $attachment_id = wp_insert_attachment( $attachment_data, $dest_file );
527 if ( is_wp_error( $attachment_id ) ) {
528 error_log( 'FluentImport: Failed to create attachment: ' . $attachment_id->get_error_message() );
529 return [];
530 }
531
532 // Generate attachment metadata
533 $attachment_metadata = wp_generate_attachment_metadata( $attachment_id, $dest_file );
534 wp_update_attachment_metadata( $attachment_id, $attachment_metadata );
535
536 $media = wp_prepare_attachment_for_js( $attachment_id );
537 return [
538 'id' => $attachment_id,
539 'title' => $media['title'],
540 'url' => $media['url'],
541 ];
542 }
543 }
544
545