PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / addons / csv / ajax / import.php

import.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at addons/csv/ajax/import.php

1,614 lines 62.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\Addons\Csv\Ajax;
4
5 use StoreEngine\Classes\AbstractAjaxHandler;
6 use StoreEngine\Classes\Attribute;
7 use StoreEngine\Classes\EventStreamServer;
8 use StoreEngine\Classes\Price;
9 use StoreEngine\Classes\Product\SimpleProduct;
10 use StoreEngine\Classes\Product\VariableProduct;
11 use StoreEngine\Classes\ProductFactory;
12 use StoreEngine\Classes\Variation;
13 use StoreEngine\Database;
14 use StoreEngine\Utils\Formatting;
15 use StoreEngine\Utils\Helper;
16 use WP_Error;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 class Import extends AbstractAjaxHandler {
23
24 protected string $namespace = STOREENGINE_PLUGIN_SLUG . '_csv';
25
26 protected static EventStreamServer $sse;
27
28 protected array $orders_key_mapping = [
29 'Order Key' => 'order_key',
30 'Order Status' => 'order_status',
31 'Customer Email' => 'customer_email',
32 'Order Currency' => 'order_currency',
33 'Order Subtotal' => 'order_subtotal',
34 'Order Total' => 'order_total',
35 'Order Tax' => 'order_tax',
36 'Date Created (GMT)' => 'date_created_gmt',
37 'Date Updated (GMT)' => 'date_updated_gmt',
38 'Date Placed (GMT)' => 'date_placed_gmt',
39 'Date Paid (GMT)' => 'order_paid_date_gmt',
40 'Payment Method (name)' => 'payment_method',
41 'Payment Method (title)' => 'payment_method_title',
42 'Transaction ID' => 'transaction_id',
43 'IP Address' => 'ip_address',
44 'User Agent' => 'user_agent',
45 'Customer Note' => 'customer_note',
46 'Cart Hash' => 'cart_hash',
47 'Meta Data' => 'meta_data',
48 ];
49
50 protected array $customers_key_mapping = [
51 'Username' => 'username',
52 'First Name' => 'first_name',
53 'Last Name' => 'last_name',
54 'Nick Name' => 'nickname',
55 'Display Name' => 'display_name',
56 'Email' => 'email',
57 'Website' => 'url',
58 'Biographical Info' => 'description',
59 'Registered Date (GMT)' => 'user_registered_date',
60 'Roles' => 'roles',
61 'Billing First Name' => 'billing_first_name',
62 'Billing Last Name' => 'billing_last_name',
63 'Billing Company' => 'billing_company',
64 'Billing Address 1' => 'billing_address_1',
65 'Billing Address 2' => 'billing_address_2',
66 'Billing City' => 'billing_city',
67 'Billing State' => 'billing_state',
68 'Billing Postcode' => 'billing_postcode',
69 'Billing Country' => 'billing_country',
70 'Billing Email' => 'billing_email',
71 'Billing Phone' => 'billing_phone',
72 'Shipping First Name' => 'shipping_first_name',
73 'Shipping Last Name' => 'shipping_last_name',
74 'Shipping Company' => 'shipping_company',
75 'Shipping Address 1' => 'shipping_address_1',
76 'Shipping Address 2' => 'shipping_address_2',
77 'Shipping City' => 'shipping_city',
78 'Shipping State' => 'shipping_state',
79 'Shipping Postcode' => 'shipping_postcode',
80 'Shipping Country' => 'shipping_country',
81 'Shipping Email' => 'shipping_email',
82 'Shipping Phone' => 'shipping_phone',
83 'Email Subscribe' => 'subscribe_to_email',
84 ];
85
86 protected array $products_key_mapping = [
87 'ID' => 'id',
88 'Type' => 'type',
89 'Name' => 'name',
90 'Slug' => 'slug',
91 'Published' => 'published_date_gmt',
92 'Description' => 'content',
93 'Is Hide' => 'hide',
94 'Shipping Type' => 'shipping_type',
95 'Weight' => 'weight',
96 'Weight Unit' => 'weight_unit',
97 'Product Categories' => 'product_categories',
98 'Product Tags' => 'product_tags',
99 'Images' => 'product_gallery',
100 ];
101
102 protected array $access_groups_key_mapping = [
103 'Access Group Name' => 'name',
104 'Unauthorized Access Message' => 'message',
105 'Protected Content Type' => 'content_type',
106 'Specific Content Items' => 'specific_content_items',
107 'Excluded Content Items' => 'excluded_content_items',
108 'Downloadable files' => 'downloadable_files',
109 'Enable Redirect URL' => 'enable_redirect_url',
110 'Redirect URL' => 'redirect_url',
111 'Enable Access Group Expiration' => 'enable_expiration',
112 'Expiration Date type' => 'expiration_date_type',
113 'Expiration Relative Date' => 'expiration_relative_date',
114 'Expiration Specific Date' => 'expiration_specific_date',
115 'User Role Sync' => 'user_role_sync',
116 'Priority' => 'priority',
117 ];
118
119 protected array $subscriptions_key_mapping = [
120 'Subscription Key' => 'subscription_key',
121 'Subscription Status' => 'subscription_status',
122 'Customer Email' => 'customer_email',
123 'Subscription Currency' => 'subscription_currency',
124 'Subscription Subtotal' => 'subscription_subtotal',
125 'Subscription Total' => 'subscription_total',
126 'Subscription Tax' => 'subscription_tax',
127 'Date Created (GMT)' => 'date_created_gmt',
128 'Date Updated (GMT)' => 'date_updated_gmt',
129 'Date Placed (GMT)' => 'date_placed_gmt',
130 'Date Paid (GMT)' => 'subscription_paid_date_gmt',
131 'Payment Method (name)' => 'payment_method',
132 'Payment Method (title)' => 'payment_method_title',
133 'IP Address' => 'ip_address',
134 'User Agent' => 'user_agent',
135 'Customer Note' => 'customer_note',
136 'Cart Hash' => 'cart_hash',
137 'Parent Order Key' => 'parent_order_key',
138 'Child Order Keys' => 'child_order_keys',
139 'Start Date (GMT)' => 'start_date',
140 'Has Trial' => 'has_trial',
141 'Trial Days' => 'trial_days',
142 'Trial End Date (GMT)' => 'trial_end_date',
143 'Next Payment Date (GMT)' => 'next_payment_date',
144 'Meta Data' => 'meta_data',
145 ];
146
147 public function __construct() {
148 $this->actions = [
149 'import' => [
150 'callback' => [ $this, 'import' ],
151 'capability' => 'manage_options',
152 'fields' => [
153 'type' => 'string',
154 ],
155 ],
156 'import_process' => [
157 'callback' => [ $this, 'import_process' ],
158 'capability' => 'manage_options',
159 'fields' => [
160 'filename' => 'string',
161 'type' => 'string',
162 'index' => 'integer',
163 ],
164 ],
165 'download_sample_file' => [
166 'callback' => [ $this, 'download_sample_file' ],
167 'capability' => 'manage_options',
168 'fields' => [
169 'type' => 'string',
170 ],
171 ],
172 ];
173 }
174
175 public function import( array $payload ) {
176 if ( ! isset( $_FILES['file'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
177 wp_send_json_error( __( 'No file was uploaded.', 'storeengine' ) );
178 }
179
180 $type = $payload['type'];
181 if ( ! in_array( $type, [ 'products', 'orders', 'customers', 'access_groups', 'subscriptions' ], true ) ) {
182 wp_send_json_error( __( 'Invalid type!', 'storeengine' ) );
183 }
184
185 $file = $_FILES['file']; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
186 $folder = Helper::get_upload_dir() . '/csv/';
187
188 if ( ! file_exists( $folder ) ) {
189 wp_mkdir_p( $folder );
190 }
191
192 $max_size = 20 * 1024 * 1024; // 20 MB
193 if ( $file['size'] > $max_size ) {
194 wp_send_json_error( __( 'File too large. Maximum size is 20MB.', 'storeengine' ) );
195 }
196
197 $ext = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
198 if ( 'csv' !== $ext ) {
199 wp_send_json_error( __( 'Only CSV files are allowed.', 'storeengine' ) );
200 }
201
202 $filename = sanitize_file_name( wp_basename( $file['name'] ) );
203 $filetype = wp_check_filetype_and_ext( $file['tmp_name'], $filename, [ 'csv' => 'text/csv' ] );
204 if ( ! isset( $filetype['ext'] ) || $filetype['ext'] !== 'csv' ) {
205 wp_send_json_error( 'Invalid file type. Only CSV files are allowed.' );
206 }
207
208 $valid_csv = \StoreEngine\Addons\Csv\Utils\Import::validate_csv_structure( $file['tmp_name'], array_keys( $this->{"{$type}_key_mapping"} ) );
209 if ( is_wp_error( $valid_csv ) ) {
210 wp_send_json_error( $valid_csv->get_error_message() );
211 }
212
213 $target = trailingslashit( $folder ) . $filename;
214
215 // @TODO Use wp_handle_upload()..
216 if ( ! move_uploaded_file( $file['tmp_name'], $target ) ) { // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
217 wp_send_json_error( __( 'Failed to move uploaded file.', 'storeengine' ) );
218 }
219
220 wp_send_json_success( [
221 'filename' => $filename,
222 ] );
223 }
224
225 public function import_process( array $payload ) {
226 if ( ! isset( $payload['type'] ) ) {
227 wp_send_json_error( __( 'Type is required.', 'storeengine' ) );
228 }
229
230 $type = $payload['type'];
231 if ( ! in_array( $type, [ 'products', 'orders', 'customers', 'access_groups', 'subscriptions' ], true ) ) {
232 wp_send_json_error( __( 'Invalid type!', 'storeengine' ) );
233 }
234
235 if ( 'access_groups' === $type && ! Helper::get_addon_active_status( 'membership' ) ) {
236 wp_send_json_error( __( 'Please active Membership addon to import Access groups.', 'storeengine' ) );
237 }
238
239 if ( 'subscriptions' === $type && ! Helper::get_addon_active_status( 'subscription' ) ) {
240 wp_send_json_error( __( 'Please active Subscription addon to import Subscriptions.', 'storeengine' ) );
241 }
242
243 if ( ! isset( $payload['filename'] ) ) {
244 wp_send_json_error( __( 'Filename is required.', 'storeengine' ) );
245 }
246 $filename = $payload['filename'];
247
248 self::$sse = new EventStreamServer();
249 self::$sse->listen( function () use ( $type, $filename, $payload ) {
250 /**
251 * @see import_products()
252 * @see import_orders()
253 * @see import_customers()
254 * @see import_access_groups()
255 * @see import_subscriptions()
256 */
257 $this->{"import_$type"}( $filename, $payload['index'] ?? null );
258 } );
259 }
260
261 private function import_products( string $filename, ?int $start_index = null ) {
262 if ( null === $start_index ) {
263 $this->chunk_products( $filename );
264
265 return;
266 }
267
268 $this->import_chunk_products( $filename, $start_index );
269 }
270
271 private function chunk_products( string $filename ) {
272 $filepath = Helper::get_upload_dir() . '/csv/' . $filename;
273 $fp = fopen( $filepath, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
274 if ( ! $fp ) {
275 self::$sse->emitEvent( [
276 'type' => 'error',
277 'message' => __( 'Unable to open file for reading.', 'storeengine' ),
278 ], true );
279 }
280
281 $products_key_mapping = $this->products_key_mapping;
282 $prices_key_mapping = [
283 'Price name' => 'name',
284 'Price type' => 'type',
285 'Price' => 'price',
286 'Compare price' => 'compare_price',
287 'Setup Fee' => 'setup_fee',
288 'Setup Fee name' => 'setup_fee_name',
289 'Setup Fee price' => 'setup_fee_price',
290 'Setup Fee type' => 'setup_fee_type',
291 'Trial' => 'trial',
292 'Trial days' => 'trial_days',
293 'Expire' => 'expire',
294 'Expire days' => 'expire_days',
295 'Payment duration' => 'payment_duration',
296 'Payment duration type' => 'payment_duration_type',
297 'Upgradeable' => 'upgradeable',
298 'Order' => 'order',
299 ];
300 $downloads_key_mapping = [
301 'Download ID' => 'id',
302 'Download Name' => 'name',
303 'Download URL' => 'file',
304 'Download Status' => 'enabled',
305 ];
306 $variations_key_mapping = [
307 'Variation Price' => 'price',
308 'Variation Image' => 'featured_image',
309 'Variation Attributes' => 'attributes',
310 'Price Index' => 'price_index',
311 'Variation SKU' => 'sku',
312 'Variation Barcode' => 'barcode',
313 'Variation Cost' => 'cost_price',
314 'Variation Stock' => 'stock_quantity',
315 ];
316
317 $products = [];
318 $current_section_header = 'product';
319 $header_row = [];
320 $recent_product = null;
321 while ( ( $row = fgetcsv( $fp ) ) !== false ) {
322 if ( empty( $row ) || ( 1 === count( array_unique( $row ) ) && empty( $row[0] ) ) ) {
323 continue;
324 }
325
326 if ( ( in_array( 'Name', $row, true ) && in_array( 'Slug', $row, true ) ) || in_array( '# Product Entity', $row, true ) ) {
327 $current_section_header = 'product';
328 $header_row = [
329 'ID',
330 'Type',
331 'Name',
332 'Slug',
333 'Published',
334 'Description',
335 'Is Hide',
336 'Shipping Type',
337 'Weight',
338 'Weight Unit',
339 'Product Categories',
340 'Product Tags',
341 'Images',
342 ];
343 continue;
344 } elseif ( in_array( 'Price name', $row, true ) && in_array( 'Price type', $row, true ) ) {
345 $current_section_header = 'price';
346 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 16 ) );
347 continue;
348 } elseif ( in_array( 'Download ID', $row, true ) && in_array( 'Download Name', $row, true ) ) {
349 $current_section_header = 'download';
350 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 4 ) );
351 continue;
352 } elseif ( in_array( 'Variation Price', $row, true ) && in_array( 'Variation Image', $row, true ) ) {
353 $current_section_header = 'variation';
354 // Header may include the new optional Variation SKU/Barcode/Cost/Stock columns.
355 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 8 ) );
356 continue;
357 }
358
359 if ( $current_section_header === 'product' ) {
360 $recent_product = sanitize_text_field( $row[ array_search( 'Slug', $header_row, true ) ] );
361 $data = array_slice( $row, 0, 13 );
362 $data = array_combine( $header_row, array_map( function ( $value, $key ) {
363 return 5 === $key ? wp_kses_post( $value ) : sanitize_text_field( $value );
364 }, $data, array_keys( $data ) ) );
365 $products[ $recent_product ] = Helper::rename_array_keys( $data, $products_key_mapping );
366 } elseif ( $current_section_header === 'price' ) {
367 $products[ $recent_product ]['prices'][] = Helper::rename_array_keys(
368 array_combine( $header_row, array_map( 'sanitize_text_field', array_slice( $row, 0, 16 ) ) ),
369 $prices_key_mapping
370 );
371 } elseif ( $current_section_header === 'download' ) {
372 $products[ $recent_product ]['downloads'][] = Helper::rename_array_keys(
373 array_combine( $header_row, array_map( 'sanitize_text_field', array_slice( $row, 0, 4 ) ) ),
374 $downloads_key_mapping
375 );
376 } elseif ( $current_section_header === 'variation' ) {
377 $slice = array_slice( $row, 0, count( $header_row ) );
378 // Normalize length to header — pad missing optional cells with empty strings.
379 while ( count( $slice ) < count( $header_row ) ) {
380 $slice[] = '';
381 }
382 $products[ $recent_product ]['variations'][] = Helper::rename_array_keys(
383 array_combine( $header_row, array_map( 'sanitize_text_field', $slice ) ),
384 $variations_key_mapping
385 );
386 }
387 }
388 fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
389 wp_delete_file( $filepath );
390
391 $expire = 60 * 60 * 4;
392 $chunked_products = array_chunk( $products, 30, true );
393 $filename = $this->get_filename_without_extension( $filename );
394 set_transient( 'storeengine_csv_products_' . $filename . '_count', count( $products ), $expire );
395 foreach ( $chunked_products as $index => $chunk ) {
396 set_transient( 'storeengine_csv_products_' . $filename . '_' . $index, $chunk, $expire );
397 }
398
399 self::$sse->emitEvent( [
400 'type' => 'chunk-completed',
401 'total' => count( $chunked_products ),
402 ], true );
403 }
404
405 private function import_chunk_products( string $filename, $start_index ) {
406 $filename = $this->get_filename_without_extension( $filename );
407 $products = get_transient( 'storeengine_csv_products_' . $filename . '_' . $start_index );
408 if ( ! $products ) {
409 self::$sse->emitEvent( [
410 'type' => 'error',
411 'message' => __( 'Unable to retrieve chunked products.', 'storeengine' ),
412 ], true );
413 }
414
415 $index = ( $start_index * 30 ) + 1;
416 $products_count = get_transient( 'storeengine_csv_products_' . $filename . '_count' );
417 foreach ( $products as $product_slug => $product_data ) {
418 $posts = get_posts( [
419 'name' => $product_slug,
420 'post_type' => 'storeengine_product',
421 'post_status' => 'publish',
422 'posts_per_page' => 1,
423 ] );
424 $has_product = ! empty( $posts );
425 if ( $has_product ) {
426 continue;
427 }
428
429 $product = ProductFactory::getProduct( 0, $product_data['type'] );
430
431 $setters = $product_data;
432 unset( $setters['product_categories'] );
433 unset( $setters['product_tags'] );
434 unset( $setters['product_gallery'] );
435 unset( $setters['id'] );
436 foreach ( $setters as $key => $value ) {
437 $method = "set_$key";
438 if ( method_exists( $product, $method ) ) {
439 $product->$method( $value );
440 }
441 }
442 $product->save();
443
444 if ( ! empty( $product_data['product_gallery'] ) ) {
445 $images = explode( ',', $product_data['product_gallery'] );
446 $gallery_ids = [];
447 foreach ( $images as $image ) {
448 $attachment_id = $this->remote_to_local_image( trim( $image ) );
449 if ( is_wp_error( $attachment_id ) ) {
450 self::$sse->emitEvent( [
451 'type' => 'error',
452 'message' => $attachment_id->get_error_messages(),
453 ] );
454 continue;
455 }
456 $gallery_ids[] = $attachment_id;
457 }
458 $product->update_metadata( 'product_gallery_ids', $gallery_ids );
459 }
460
461 if ( ! empty( $product_data['product_categories'] ) ) {
462 $categories = explode( ',', $product_data['product_categories'] );
463 $terms = [];
464 foreach ( $categories as $category ) {
465 $category = trim( $category );
466 $sub_categories = explode( '>', $category );
467 if ( is_array( $sub_categories ) && ! empty( $sub_categories ) ) {
468 $parent_category = 0;
469 $j = 0;
470 while ( $j < count( $sub_categories ) ) {
471 $term = wp_insert_term( $sub_categories[ $j ], 'storeengine_product_category', [
472 'parent' => $parent_category,
473 ] );
474 $j ++;
475 if ( is_wp_error( $term ) ) {
476 self::$sse->emitEvent( [
477 'type' => 'error',
478 'message' => $term->get_error_message(),
479 ] );
480 continue;
481 }
482 $terms[] = $term['term_id'];
483 $parent_category = $term['term_id'];
484 }
485 continue;
486 }
487
488 $term = get_term_by( 'name', $category, 'storeengine_product_category' );
489 if ( $term ) {
490 $terms[] = $term->term_id;
491 } else {
492 $term = wp_insert_term( $category, 'storeengine_product_category' );
493 if ( is_wp_error( $term ) ) {
494 self::$sse->emitEvent( [
495 'type' => 'error',
496 'message' => $term->get_error_message(),
497 ] );
498 continue;
499 }
500 $terms[] = $term['term_id'];
501 }
502 }
503
504 wp_set_object_terms( $product->get_id(), $terms, 'storeengine_product_category' );
505 }
506
507 if ( ! empty( $product_data['product_tags'] ) ) {
508 $tags = explode( ',', $product_data['product_tags'] );
509 $terms = [];
510 foreach ( $tags as $tag ) {
511 $tag = trim( $tag );
512 $term = get_term_by( 'name', $tag, 'storeengine_product_tag' );
513 if ( $term ) {
514 $terms[] = $term->term_id;
515 } else {
516 $term = wp_insert_term( $tag, 'storeengine_product_tag' );
517 if ( is_wp_error( $term ) ) {
518 self::$sse->emitEvent( [
519 'type' => 'error',
520 'message' => $term->get_error_message(),
521 ] );
522 continue;
523 }
524 $terms[] = $term['term_id'];
525 }
526 }
527
528 wp_set_object_terms( $product->get_id(), $terms, 'storeengine_product_tag' );
529 }
530
531 $prices = [];
532 if ( isset( $product_data['prices'] ) && is_array( $product_data['prices'] ) ) {
533 foreach ( $product_data['prices'] as $price_data ) {
534 $price = new Price();
535 $price->set_product_id( $product->get_id() );
536 foreach ( $price_data as $price_key => $value ) {
537 if ( ! empty( $value ) ) {
538 $price->{"set_$price_key"}( $value );
539 }
540 }
541
542 $price->save();
543 $prices[] = $price->get_id();
544 }
545 }
546
547 if ( isset( $product_data['downloads'] ) && is_array( $product_data['downloads'] ) ) {
548 $product->set_downloadable_files( $product_data['downloads'] );
549 $product->save();
550 }
551
552 if ( isset( $product_data['variations'] ) && is_array( $product_data['variations'] ) ) {
553 $taxonomy_labels = [];
554 $variation_data = [];
555 foreach ( $product_data['variations'] as $v_data ) {
556 $variation_attributes = explode( ',', $v_data['attributes'] );
557 $variant_data = [];
558 foreach ( $variation_attributes as $variation_attribute ) {
559 $variation_attribute = trim( $variation_attribute );
560 $variant = explode( ':', $variation_attribute );
561 if ( count( $variant ) !== 2 ) {
562 continue;
563 }
564 $taxonomy_labels[] = strtolower( trim( $variant[0] ) );
565 $variant_data[] = [
566 'taxonomy' => strtolower( trim( $variant[0] ) ),
567 'value' => trim( $variant[1] ),
568 ];
569 }
570 $variation_data[] = [
571 'price' => $v_data['price'],
572 'price_index' => $v_data['price_index'],
573 'featured_image' => $v_data['featured_image'],
574 'attributes' => $variant_data,
575 ];
576 }
577
578 $taxonomy_labels = array_unique( $taxonomy_labels );
579 $taxonomy_labels_formatter = implode( ',', array_fill( 0, count( $taxonomy_labels ), '%s' ) );
580 $attribute_taxonomies_exists = [];
581
582 global $wpdb;
583
584 // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
585 $results = $wpdb->get_results( $wpdb->prepare(
586 "SELECT attribute_id, attribute_label FROM {$wpdb->prefix}storeengine_attribute_taxonomies
587 WHERE attribute_label IN ($taxonomy_labels_formatter)",
588 ...$taxonomy_labels
589 ) );
590 // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
591
592 foreach ( $results as $result ) {
593 $attribute_taxonomies_exists[ $result->attribute_label ] = $result->attribute_id;
594 }
595
596 $taxonomy_names = [];
597 foreach ( $taxonomy_labels as $taxonomy_label ) {
598 if ( isset( $attribute_taxonomies_exists[ $taxonomy_label ] ) ) {
599 continue;
600 }
601 $name = sanitize_title( $taxonomy_label );
602 $prefixed_name = Helper::get_attribute_taxonomy_name( $name );
603 while ( strlen( $prefixed_name ) > 30 ) {
604 $previous = $name;
605
606 // Match and remove the last "word" including its delimiter
607 $name = preg_replace( '/[\s\-_]+[^-\s_]+$/u', '', trim( $name ) );
608
609 // If nothing changed, break to avoid infinite loop
610 if ( $name === $previous ) {
611 break;
612 }
613 $prefixed_name = Helper::get_attribute_taxonomy_name( $name );
614 }
615
616 $i = 1;
617 while ( taxonomy_exists( $prefixed_name ) ) {
618 $name .= $i;
619 $prefixed_name = Helper::get_attribute_taxonomy_name( $name );
620 $i ++;
621 }
622 $taxonomy_names[ $taxonomy_label ] = $name;
623 }
624
625 foreach ( $taxonomy_names as $taxonomy_label => $taxonomy_name ) {
626 $attribute = new Attribute();
627 $attribute->set_label( $taxonomy_label );
628 $attribute->set_name( $taxonomy_name );
629 $attribute->set_orderby( 'custom-ordering' );
630 $attribute->save();
631 }
632
633 $all_product_taxonomies = [];
634 foreach ( $variation_data as $variation_datum ) {
635 $variation_terms = [];
636 foreach ( $variation_datum['attributes'] as $variant_datum ) {
637 Database::register_attribute_taxonomy( $variant_datum['taxonomy'], $variant_datum['taxonomy'], true );
638 $term = get_term_by( 'name', $variant_datum['value'], Helper::get_attribute_taxonomy_name( $variant_datum['taxonomy'] ) );
639 $term_id = $term ? $term->term_id : null;
640 if ( ! $term ) {
641 $term = wp_insert_term( $variant_datum['value'], Helper::get_attribute_taxonomy_name( $variant_datum['taxonomy'] ) );
642 if ( is_wp_error( $term ) ) {
643 self::$sse->emitEvent( [
644 'type' => 'error',
645 'message' => $term->get_error_message(),
646 ] );
647 continue;
648 }
649 $term_id = $term['term_id'];
650 }
651 $variation_terms[] = $term_id;
652 $all_product_taxonomies[] = Helper::get_attribute_taxonomy_name( $variant_datum['taxonomy'] );
653 wp_set_object_terms( $product->get_id(), $term_id, Helper::get_attribute_taxonomy_name( $variant_datum['taxonomy'] ) );
654 }
655
656 $variation = new Variation();
657 $variation->set_product_id( $product->get_id() );
658 $variation->set_attributes( $variation_terms );
659 if ( ! empty( $variation_datum['price'] ) ) {
660 $variation->set_price( (float) $variation_datum['price'] );
661 }
662 if ( ! empty( $variation_datum['featured_image'] ) ) {
663 $attachment_id = $this->remote_to_local_image( trim( $variation_datum['featured_image'] ) );
664 if ( is_wp_error( $attachment_id ) ) {
665 self::$sse->emitEvent( [
666 'type' => 'error',
667 'message' => $attachment_id->get_error_messages(),
668 ] );
669 } else {
670 $variation->set_featured_image( $attachment_id );
671 }
672 }
673 if ( ! empty( $variation_datum['price_index'] ) ) {
674 $variation->set_price_id( $prices[ max( absint( $variation_datum['price_index'] ) - 1, 0 ) ] );
675 }
676
677 if ( ! empty( $variation_datum['sku'] ) && method_exists( $variation, 'set_sku' ) ) {
678 $variation->set_sku( sanitize_text_field( (string) $variation_datum['sku'] ) );
679 }
680 if ( ! empty( $variation_datum['barcode'] ) && method_exists( $variation, 'set_barcode' ) ) {
681 $variation->set_barcode( sanitize_text_field( (string) $variation_datum['barcode'] ) );
682 }
683 if ( isset( $variation_datum['cost_price'] ) && '' !== $variation_datum['cost_price'] && method_exists( $variation, 'set_cost_price' ) ) {
684 $variation->set_cost_price( (float) $variation_datum['cost_price'] );
685 }
686 if ( isset( $variation_datum['stock_quantity'] ) && '' !== $variation_datum['stock_quantity'] && method_exists( $variation, 'set_stock_quantity' ) ) {
687 $qty = (int) $variation_datum['stock_quantity'];
688 $variation->set_manage_stock( true );
689 $variation->set_stock_quantity( $qty );
690 }
691
692 $variation->save();
693
694 // Let multi-location addons (e.g. inventory-pro) mirror this
695 // import into per-location stock. CSV addon stays free-only.
696 if ( isset( $variation_datum['stock_quantity'] ) && '' !== $variation_datum['stock_quantity'] && $variation->get_id() ) {
697 /**
698 * Fires after CSV import sets stock on a variation.
699 *
700 * @param int $product_id
701 * @param int $variation_id
702 * @param int $qty
703 */
704 do_action(
705 'storeengine/inventory/import_stock_set',
706 $product->get_id(),
707 $variation->get_id(),
708 (int) $variation_datum['stock_quantity']
709 );
710 }
711 }
712
713 $product->set_attributes_order( array_unique( $all_product_taxonomies ) );
714 $product->save();
715 }
716
717 self::$sse->emitEvent( [
718 'type' => 'progress',
719 'percentage' => round( ( $index / $products_count ) * 100 ),
720 ] );
721 $index ++;
722 }
723
724 delete_transient( 'storeengine_csv_products_' . $filename . '_' . $start_index );
725 wp_cache_flush();
726
727 self::$sse->emitEvent( [
728 'type' => 'completed',
729 ], true );
730 }
731
732 private function remote_to_local_image( string $url ) {
733 if ( empty( $url ) || ! wp_http_validate_url( $url ) ) {
734 return new Wp_Error( 'invalid_url', __( 'Invalid URL', 'storeengine' ) );
735 }
736
737 // Download image
738 $temp_file = download_url( $url );
739 if ( is_wp_error( $temp_file ) ) {
740 return $temp_file;
741 }
742
743 $file_array = [
744 'name' => basename( $url ),
745 'tmp_name' => $temp_file,
746 ];
747
748 // Upload to WordPress media library
749 $attachment_id = media_handle_sideload( $file_array );
750 if ( is_wp_error( $attachment_id ) ) {
751 wp_delete_file( $temp_file );
752 }
753
754 return $attachment_id;
755 }
756
757 private function import_orders( string $filename, ?int $start_index = null ) {
758 if ( null === $start_index ) {
759 $this->chunk_orders( $filename );
760
761 return;
762 }
763
764 \StoreEngine\Addons\Csv\Utils\Import::import_chunk_orders( $filename, $start_index, self::$sse );
765 }
766
767 private function chunk_orders( string $filename ) {
768 $dir = Helper::get_upload_dir() . '/csv/';
769 $filepath = $dir . $filename;
770 $fp = fopen( $filepath, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
771 if ( ! $fp ) {
772 self::$sse->emitEvent( [
773 'type' => 'error',
774 'message' => __( 'Unable to open file for reading.', 'storeengine' ),
775 ], true );
776 }
777
778 $order_key_mapping = $this->orders_key_mapping;
779 $order_item_key_mapping = [
780 'Order Item Name' => 'name',
781 'Product Slug' => 'product_slug',
782 'Product Type' => 'product_type',
783 'Price' => 'price',
784 'Quantity' => 'quantity',
785 'Subtotal' => 'subtotal',
786 'Subtotal (Tax)' => 'subtotal_tax',
787 'Total' => 'total',
788 'Total (Tax)' => 'total_tax',
789 'Product Shipping Type' => 'shipping_type',
790 'Is Autocomplete' => 'digital_auto_complete',
791 'Price name' => 'price_name',
792 'Price type' => 'price_type',
793 'Setup Fee' => 'setup_fee',
794 'Setup Fee name' => 'setup_fee_name',
795 'Setup Fee price' => 'setup_fee_price',
796 'Setup Fee type' => 'setup_fee_type',
797 'Trial' => 'trial',
798 'Trial days' => 'trial_days',
799 'Expire' => 'expire',
800 'Expire days' => 'expire_days',
801 'Payment duration' => 'payment_duration',
802 'Payment duration type' => 'payment_duration_type',
803 'Meta Data' => 'meta_data',
804 ];
805 $billing_address_key_mapping = [
806 'Billing First Name' => 'billing_first_name',
807 'Billing Last Name' => 'billing_last_name',
808 'Billing Company' => 'billing_company',
809 'Billing Address 1' => 'billing_address_1',
810 'Billing Address 2' => 'billing_address_2',
811 'Billing City' => 'billing_city',
812 'Billing State' => 'billing_state',
813 'Billing Postcode' => 'billing_postcode',
814 'Billing Country' => 'billing_country',
815 'Billing Email' => 'billing_email',
816 'Billing Phone' => 'billing_phone',
817 ];
818 $shipping_address_key_mapping = [
819 'Shipping First Name' => 'shipping_first_name',
820 'Shipping Last Name' => 'shipping_last_name',
821 'Shipping Company' => 'shipping_company',
822 'Shipping Address 1' => 'shipping_address_1',
823 'Shipping Address 2' => 'shipping_address_2',
824 'Shipping City' => 'shipping_city',
825 'Shipping State' => 'shipping_state',
826 'Shipping Postcode' => 'shipping_postcode',
827 'Shipping Country' => 'shipping_country',
828 'Shipping Email' => 'shipping_email',
829 'Shipping Phone' => 'shipping_phone',
830 ];
831 $coupon_key_mapping = [
832 'Coupon Code' => 'code',
833 'Coupon Amount' => 'discount',
834 'Coupon Amount (Tax)' => 'discount_tax',
835 ];
836 $tax_key_mapping = [
837 'Tax Name' => 'name',
838 'Tax Label' => 'label',
839 'Tax Amount' => 'tax_total',
840 'Tax Rate(%)' => 'rate_percent',
841 'Shipping Tax Amount' => 'shipping_tax_total',
842 ];
843 $shipping_key_mapping = [
844 'Shipping Method' => 'name',
845 'Shipping Method Title' => 'method_title',
846 'Amount' => 'total',
847 'Amount (Tax)' => 'total_tax',
848 'Tax Status' => 'tax_status',
849 ];
850 $fee_key_mapping = [
851 'Fee Name' => 'name',
852 'Fee Amount' => 'amount',
853 'Fee Amount (Tax)' => 'total_tax',
854 'Tax Status' => 'tax_status',
855 ];
856 $download_key_mapping = [
857 'Download ID' => 'download_id',
858 'Product Slug' => 'product_slug',
859 'Date Access Granted (GMT)' => 'access_granted',
860 ];
861 $refund_key_mapping = [
862 'Refund Amount' => 'amount',
863 'Refund Reason' => 'reason',
864 'Refund By' => 'refund_by',
865 ];
866
867 $orders = [];
868 $current_section_header = 'order';
869 $header_row = [];
870 $recent_order = null;
871 while ( ( $row = fgetcsv( $fp ) ) !== false ) {
872 if ( empty( $row ) || ( 1 === count( array_unique( $row ) ) && empty( $row[0] ) ) ) {
873 continue;
874 }
875
876 if ( ( in_array( 'Order Key', $row, true ) && in_array( 'Order Status', $row, true ) ) || in_array( '# Order Entity', $row, true ) ) {
877 $current_section_header = 'order';
878 $header_row = array_keys( $order_key_mapping );
879 continue;
880 } elseif ( in_array( 'Order Item Name', $row, true ) ) {
881 $current_section_header = 'order_item';
882 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 24 ) );
883 continue;
884 } elseif ( in_array( 'Billing First Name', $row, true ) && in_array( 'Billing Last Name', $row, true ) ) {
885 $current_section_header = 'billing_address';
886 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 11 ) );
887 continue;
888 } elseif ( in_array( 'Shipping First Name', $row, true ) && in_array( 'Shipping Last Name', $row, true ) ) {
889 $current_section_header = 'shipping_address';
890 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 11 ) );
891 continue;
892 } elseif ( in_array( 'Coupon Code', $row, true ) && in_array( 'Coupon Amount', $row, true ) ) {
893 $current_section_header = 'coupon';
894 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 3 ) );
895 continue;
896 } elseif ( in_array( 'Tax Name', $row, true ) && in_array( 'Tax Label', $row, true ) ) {
897 $current_section_header = 'tax';
898 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 5 ) );
899 continue;
900 } elseif ( in_array( 'Shipping Method', $row, true ) && in_array( 'Shipping Method Title', $row, true ) ) {
901 $current_section_header = 'shipping';
902 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 5 ) );
903 continue;
904 } elseif ( in_array( 'Fee Name', $row, true ) && in_array( 'Fee Amount', $row, true ) ) {
905 $current_section_header = 'fee';
906 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 4 ) );
907 continue;
908 } elseif ( in_array( 'Download ID', $row, true ) ) {
909 $current_section_header = 'download';
910 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 3 ) );
911 continue;
912 } elseif ( in_array( 'Refund Amount', $row, true ) ) {
913 $current_section_header = 'refund';
914 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 3 ) );
915 continue;
916 }
917
918 if ( 'order' === $current_section_header ) {
919 $recent_order = $row[ array_search( 'Order Key', $header_row, true ) ];
920 $order_data = array_slice( $row, 0, count( $order_key_mapping ) );
921 $orders[ $recent_order ] = Helper::rename_array_keys(
922 array_combine( $header_row, array_map( function ( $value, $key ) {
923 return 2 === $key ? sanitize_email( $value ) : sanitize_text_field( $value );
924 }, $order_data, array_keys( $order_data ) ) ),
925 $order_key_mapping
926 );
927 } elseif ( 'order_item' === $current_section_header ) {
928 $orders[ $recent_order ]['order_items'][] = Helper::rename_array_keys(
929 array_combine(
930 $header_row,
931 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $order_item_key_mapping ) ) )
932 ),
933 $order_item_key_mapping
934 );
935 } elseif ( 'billing_address' === $current_section_header ) {
936 $billing_data = array_slice( $row, 0, count( $billing_address_key_mapping ) );
937 $billing_data = array_map( function ( $value, $key ) {
938 return 9 === $key ? sanitize_email( $value ) : sanitize_text_field( $value );
939 }, $billing_data, array_keys( $billing_data ) );
940 $orders[ $recent_order ]['billing_address'][] = Helper::rename_array_keys(
941 array_combine( $header_row, $billing_data ),
942 $billing_address_key_mapping
943 );
944 } elseif ( 'shipping_address' === $current_section_header ) {
945 $shipping_data = array_slice( $row, 0, count( $shipping_address_key_mapping ) );
946 $shipping_data = array_map( function ( $value, $key ) {
947 return 9 === $key ? sanitize_email( $value ) : sanitize_text_field( $value );
948 }, $shipping_data, array_keys( $shipping_data ) );
949 $orders[ $recent_order ]['shipping_address'][] = Helper::rename_array_keys(
950 array_combine( $header_row, $shipping_data ),
951 $shipping_address_key_mapping
952 );
953 } elseif ( 'coupon' === $current_section_header ) {
954 $orders[ $recent_order ]['coupons'][] = Helper::rename_array_keys(
955 array_combine(
956 $header_row,
957 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $coupon_key_mapping ) ) )
958 ),
959 $coupon_key_mapping
960 );
961 } elseif ( 'tax' === $current_section_header ) {
962 $orders[ $recent_order ]['taxes'][] = Helper::rename_array_keys(
963 array_combine(
964 $header_row,
965 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $tax_key_mapping ) ) )
966 ),
967 $tax_key_mapping
968 );
969 } elseif ( 'shipping' === $current_section_header ) {
970 $orders[ $recent_order ]['shipping'][] = Helper::rename_array_keys(
971 array_combine(
972 $header_row,
973 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $shipping_key_mapping ) ) )
974 ),
975 $shipping_key_mapping
976 );
977 } elseif ( 'fee' === $current_section_header ) {
978 $orders[ $recent_order ]['fees'][] = Helper::rename_array_keys(
979 array_combine(
980 $header_row,
981 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $fee_key_mapping ) ) )
982 ),
983 $fee_key_mapping
984 );
985 } elseif ( 'download' === $current_section_header ) {
986 $orders[ $recent_order ]['downloads'][] = Helper::rename_array_keys(
987 array_combine(
988 $header_row,
989 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $download_key_mapping ) ) )
990 ),
991 $download_key_mapping
992 );
993 } elseif ( 'refund' === $current_section_header ) {
994 $orders[ $recent_order ]['refunds'][] = Helper::rename_array_keys(
995 array_combine(
996 $header_row,
997 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $refund_key_mapping ) ) )
998 ),
999 $refund_key_mapping
1000 );
1001 }
1002 }
1003 fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1004 wp_delete_file( $filepath );
1005
1006 $chunked_orders = \StoreEngine\Addons\Csv\Utils\Import::store_chunk_data( 'orders', $filename, $orders );
1007
1008 self::$sse->emitEvent( [
1009 'type' => 'chunk-completed',
1010 'total' => count( $chunked_orders ),
1011 ], true );
1012 }
1013
1014 private function get_filename_without_extension( string $filename ) {
1015 return pathinfo( $filename, PATHINFO_FILENAME );
1016 }
1017
1018 private function import_customers( string $filename, ?int $start_index = null ) {
1019 if ( null === $start_index ) {
1020 $this->chunk_customers( $filename );
1021
1022 return;
1023 }
1024
1025 \StoreEngine\Addons\Csv\Utils\Import::import_chunk_customers( $filename, $start_index, self::$sse );
1026 }
1027
1028 private function chunk_customers( string $filename ) {
1029 $filepath = Helper::get_upload_dir() . '/csv/' . $filename;
1030 $fp = fopen( $filepath, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1031 if ( ! $fp ) {
1032 self::$sse->emitEvent( [
1033 'type' => 'error',
1034 'message' => __( 'Unable to open file for reading.', 'storeengine' ),
1035 ], true );
1036 }
1037
1038 $key_mapping = $this->customers_key_mapping;
1039
1040 $customers = [];
1041 while ( ( $row = fgetcsv( $fp ) ) !== false ) {
1042 if ( empty( $row ) || ( 1 === count( array_unique( $row ) ) && empty( $row[0] ) ) || ( in_array( 'Username', $row, true ) && in_array( 'First Name', $row, true ) ) ) {
1043 continue;
1044 }
1045
1046 $sliced_row = array_slice( $row, 0, count( $key_mapping ) );
1047 $sliced_row = array_map( function ( $value, $key ) {
1048 return in_array( $key, [ 5, 19, 30 ], true ) ? sanitize_email( $value ) : sanitize_text_field( $value );
1049 }, $sliced_row, array_keys( $sliced_row ) );
1050 if ( count( $sliced_row ) !== count( $key_mapping ) ) {
1051 $sliced_row = array_pad( $sliced_row, count( $key_mapping ), '' );
1052 }
1053 $customers[] = Helper::rename_array_keys( array_combine( array_keys( $key_mapping ), $sliced_row ), $key_mapping );
1054 }
1055 fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1056 wp_delete_file( $filepath );
1057
1058 $chunked_customers = \StoreEngine\Addons\Csv\Utils\Import::store_chunk_data( 'customers', $filename, $customers );
1059
1060 self::$sse->emitEvent( [
1061 'type' => 'chunk-completed',
1062 'total' => count( $chunked_customers ),
1063 ], true );
1064 }
1065
1066 private function import_access_groups( string $filename, ?int $start_index = null ) {
1067 if ( null === $start_index ) {
1068 $this->chunk_access_groups( $filename );
1069
1070 return;
1071 }
1072
1073 $this->import_chunk_access_groups( $filename, $start_index );
1074 }
1075
1076 private function chunk_access_groups( string $filename ) {
1077 $filepath = Helper::get_upload_dir() . '/csv/' . $filename;
1078 $fp = fopen( $filepath, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1079 if ( ! $fp ) {
1080 self::$sse->emitEvent( [
1081 'type' => 'error',
1082 'message' => __( 'Unable to open file for reading.', 'storeengine' ),
1083 ], true );
1084 }
1085
1086 $access_group_key_mapping = $this->access_groups_key_mapping;
1087 $prices_key_mapping = [
1088 'Product name' => 'product_name',
1089 'Product slug' => 'product_slug',
1090 'Price name' => 'name',
1091 'Price type' => 'type',
1092 'Price' => 'price',
1093 'Compare price' => 'compare_price',
1094 'Setup Fee' => 'setup_fee',
1095 'Setup Fee name' => 'setup_fee_name',
1096 'Setup Fee price' => 'setup_fee_price',
1097 'Setup Fee type' => 'setup_fee_type',
1098 'Trial' => 'trial',
1099 'Trial days' => 'trial_days',
1100 'Expire' => 'expire',
1101 'Expire days' => 'expire_days',
1102 'Payment duration' => 'payment_duration',
1103 'Payment duration type' => 'payment_duration_type',
1104 'Upgradeable' => 'upgradeable',
1105 'Order' => 'order',
1106 ];
1107 $features_key_mapping = [
1108 'Feature Icon' => 'icon',
1109 'Feature Label' => 'label',
1110 ];
1111
1112 $access_groups = [];
1113 $current_section_header = 'access_group';
1114 $header_row = [];
1115 $recent_access_group = null;
1116 while ( ( $row = fgetcsv( $fp ) ) !== false ) {
1117 if ( empty( $row ) || ( 1 === count( array_unique( $row ) ) && empty( $row[0] ) ) ) {
1118 continue;
1119 }
1120
1121 if ( ( in_array( 'Access Group Name', $row, true ) && in_array( 'Unauthorized Access Message', $row, true ) ) || in_array( '# Access Group Entity', $row, true ) ) {
1122 $current_section_header = 'access_group';
1123 $header_row = array_keys( $access_group_key_mapping );
1124 continue;
1125 } elseif ( in_array( 'Price name', $row, true ) && in_array( 'Price type', $row, true ) ) {
1126 $current_section_header = 'price';
1127 $header_row = array_slice( $row, 0, count( $prices_key_mapping ) );
1128 continue;
1129 } elseif ( in_array( 'Feature Icon', $row, true ) && in_array( 'Feature Label', $row, true ) ) {
1130 $current_section_header = 'feature';
1131 $header_row = array_slice( $row, 0, count( $features_key_mapping ) );
1132 continue;
1133 }
1134
1135 if ( 'access_group' === $current_section_header ) {
1136 $last_index = array_key_last( $access_groups );
1137 $recent_access_group = null !== $last_index ? $last_index + 1 : 0;
1138 $access_groups_data = array_map( 'sanitize_text_field', array_slice( $row, 0, count( $access_group_key_mapping ) ) );
1139 $access_groups[ $recent_access_group ] = Helper::rename_array_keys( array_combine( $header_row, $access_groups_data ), $access_group_key_mapping );
1140 } elseif ( 'price' === $current_section_header ) {
1141 $prices_data = array_slice( $row, 0, count( $prices_key_mapping ) );
1142 $prices_data = array_map( 'sanitize_text_field', $prices_data );
1143 $access_groups[ $recent_access_group ]['prices'][] = Helper::rename_array_keys(
1144 array_combine(
1145 $header_row,
1146 $prices_data
1147 ),
1148 $prices_key_mapping
1149 );
1150 } elseif ( 'feature' === $current_section_header ) {
1151 $features_data = array_slice( $row, 0, count( $features_key_mapping ) );
1152 $features_data = array_map( 'sanitize_text_field', $features_data );
1153 $access_groups[ $recent_access_group ]['features'][] = Helper::rename_array_keys(
1154 array_combine(
1155 $header_row,
1156 $features_data
1157 ),
1158 $features_key_mapping
1159 );
1160 }
1161 }
1162 fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1163 wp_delete_file( $filepath );
1164
1165 $expire = 60 * 60 * 4;
1166 $chunked_access_groups = array_chunk( $access_groups, 30, true );
1167 $filename = $this->get_filename_without_extension( $filename );
1168 set_transient( 'storeengine_csv_access_groups_' . $filename . '_count', count( $access_groups ), $expire );
1169 foreach ( $chunked_access_groups as $index => $chunk ) {
1170 set_transient( 'storeengine_csv_access_groups_' . $filename . '_' . $index, $chunk, $expire );
1171 }
1172
1173 self::$sse->emitEvent( [
1174 'type' => 'chunk-completed',
1175 'total' => count( $chunked_access_groups ),
1176 ], true );
1177 }
1178
1179 private function import_chunk_access_groups( string $filename, int $start_index ) {
1180 $filename = $this->get_filename_without_extension( $filename );
1181 $access_groups = get_transient( 'storeengine_csv_access_groups_' . $filename . '_' . $start_index );
1182 if ( ! $access_groups ) {
1183 self::$sse->emitEvent( [
1184 'type' => 'error',
1185 'message' => __( 'Unable to retrieve chunked access groups.', 'storeengine' ),
1186 ], true );
1187 }
1188
1189 $rules = [
1190 'Entire Website' => 'basic-global',
1191 'All Posts' => 'post|all',
1192 'All Categories Archive' => 'post|all|taxarchive|category',
1193 'All Tags Archive' => 'post|all|taxarchive|post_tag',
1194 'All Pages' => 'page|all',
1195 'Specific Posts/ Pages/ Taxonomies' => 'specifics',
1196 ];
1197 $access_groups_count = get_transient( 'storeengine_csv_access_groups_' . $filename . '_count' );
1198 $index = ( $start_index * 30 ) + 1;
1199 foreach ( $access_groups as $access_group_data ) {
1200 $post_id = wp_insert_post( [
1201 'post_title' => $access_group_data['name'],
1202 'post_type' => 'storeengine_groups',
1203 'post_status' => 'publish',
1204 ] );
1205 if ( ! $post_id ) {
1206 continue;
1207 }
1208
1209 update_post_meta( $post_id, '_storeengine_membership_authorization', [
1210 'type' => Formatting::string_to_bool( $access_group_data['enable_redirect_url'] ) ? 'redirect' : 'message',
1211 'redirect_url' => $access_group_data['redirect_url'],
1212 'message' => $access_group_data['message'],
1213 ] );
1214 update_post_meta( $post_id, '_storeengine_membership_expiration', [
1215 'is_enable_expiration' => Formatting::string_to_bool( $access_group_data['enable_expiration'] ),
1216 'date_type' => $access_group_data['expiration_date_type'],
1217 'fixed_date_duration' => $access_group_data['expiration_relative_date'],
1218 'specific_date' => $access_group_data['expiration_specific_date'],
1219 ] );
1220
1221 $specific_items = [];
1222 if ( ! empty( $access_group_data['specific_content_items'] ) ) {
1223 foreach ( ( explode( ',', $access_group_data['specific_content_items'] ) ?? [] ) as $specific_content_item ) {
1224 $label_value = $this->get_label_value( $specific_content_item );
1225 if ( is_array( $label_value ) ) {
1226 $specific_items[] = $label_value;
1227 }
1228 }
1229 }
1230
1231 update_post_meta( $post_id, '_storeengine_membership_content_protect_types', [
1232 'rules' => array_map( fn( $type ) => $rules[ trim( $type ) ], explode( ',', $access_group_data['content_type'] ) ),
1233 'specifics' => $specific_items,
1234 ] );
1235
1236 $excluded_items = [];
1237 if ( ! empty( $access_group_data['excluded_content_items'] ) ) {
1238 foreach ( ( explode( ',', $access_group_data['excluded_content_items'] ) ?? [] ) as $excluded_content_item ) {
1239 $label_value = $this->get_label_value( $excluded_content_item );
1240 if ( is_array( $label_value ) ) {
1241 $excluded_items[] = $label_value;
1242 }
1243 }
1244 }
1245 update_post_meta( $post_id, '_storeengine_membership_content_protect_excluded_items', $excluded_items );
1246
1247 $roles = [];
1248 foreach ( ( explode( ',', $access_group_data['user_role_sync'] ) ?? [] ) as $user_role ) {
1249 if ( isset( wp_roles()->role_names[ trim( $user_role ) ] ) ) {
1250 $roles[] = [
1251 'value' => trim( $user_role ),
1252 'label' => wp_roles()->role_names[ trim( $user_role ) ],
1253 ];
1254 }
1255 }
1256 update_post_meta( $post_id, '_storeengine_membership_user_roles', $roles );
1257 update_post_meta( $post_id, '_storeengine_membership_priority', $access_group_data['priority'] );
1258
1259 $downloadable_items = [];
1260 if ( ! empty( $access_group_data['downloadable_files'] ) ) {
1261 foreach ( ( explode( ',', $access_group_data['downloadable_files'] ) ?? [] ) as $downloadable_file_url ) {
1262 $downloadable_file_url = trim( $downloadable_file_url );
1263 $attachment_id = $this->remote_to_local_image( $downloadable_file_url );
1264
1265 if ( ! is_wp_error( $attachment_id ) ) {
1266 $downloadable_items[] = $attachment_id;
1267 }
1268 }
1269 }
1270 update_post_meta( $post_id, '_storeengine_membership_attachments', $downloadable_items );
1271
1272 update_post_meta( $post_id, '_storeengine_membership_features', $access_group_data['features'] ?? [] );
1273
1274 foreach ( $access_group_data['prices'] as $price_data ) {
1275 $posts = get_posts( [
1276 'name' => $price_data['product_slug'],
1277 'post_type' => 'storeengine_product',
1278 'post_status' => 'publish',
1279 'posts_per_page' => 1,
1280 ] );
1281 $has_product = ! empty( $posts );
1282
1283 $price_obj = null;
1284 if ( $has_product ) {
1285 $product = Helper::get_product( $posts[0]->ID );
1286 foreach ( $product->get_prices() as $price ) {
1287 if ( (float) $price_data['price'] === $price->get_price() && $price_data['name'] === $price->get_name() && $price_data['type'] === $price->get_type() ) {
1288 $price_obj = $price;
1289 break;
1290 }
1291 }
1292 } else {
1293 $product = new SimpleProduct();
1294 $product->set_name( $price_data['product_name'] );
1295 $product->set_slug( $price_data['product_slug'] );
1296 $product->set_status( 'publish' );
1297 $product->set_shipping_type( 'digital' );
1298 $product->set_digital_auto_complete( true );
1299 $product->save();
1300 $price = new Price();
1301 $price->set_product_id( $product->get_id() );
1302 foreach ( $price_data as $key => $value ) {
1303 if ( method_exists( $price, "set_$key" ) ) {
1304 $price->{"set_$key"}( $value );
1305 }
1306 }
1307 $price->save();
1308 $price_obj = $price;
1309 }
1310
1311 if ( $price_obj ) {
1312 $price_obj->add_integration( 'storeengine/membership-addon', $post_id );
1313 }
1314 }
1315
1316 self::$sse->emitEvent( [
1317 'type' => 'progress',
1318 'percentage' => round( ( $index / $access_groups_count ) * 100 ),
1319 ] );
1320 $index ++;
1321 }
1322
1323 delete_transient( 'storeengine_csv_access_groups_' . $filename . '_' . $start_index );
1324 wp_cache_flush();
1325
1326 self::$sse->emitEvent( [
1327 'type' => 'completed',
1328 ], true );
1329 }
1330
1331 private function import_subscriptions( string $filename, ?int $start_index = null ) {
1332 if ( null === $start_index ) {
1333 $this->chunk_subscriptions( $filename );
1334
1335 return;
1336 }
1337
1338 \StoreEngine\Addons\Csv\Utils\Import::import_chunk_subscriptions( $filename, $start_index, self::$sse );
1339 }
1340
1341 private function chunk_subscriptions( string $filename ) {
1342 $filepath = Helper::get_upload_dir() . '/csv/' . $filename;
1343 $fp = fopen( $filepath, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1344 if ( ! $fp ) {
1345 self::$sse->emitEvent( [
1346 'type' => 'error',
1347 'message' => __( 'Unable to open file for reading.', 'storeengine' ),
1348 ], true );
1349 }
1350
1351 $subscription_key_mapping = $this->subscriptions_key_mapping;
1352 $order_item_key_mapping = [
1353 'Item Name' => 'name',
1354 'Product Slug' => 'product_slug',
1355 'Product Type' => 'product_type',
1356 'Price' => 'price',
1357 'Quantity' => 'quantity',
1358 'Subtotal' => 'subtotal',
1359 'Subtotal (Tax)' => 'subtotal_tax',
1360 'Total' => 'total',
1361 'Total (Tax)' => 'total_tax',
1362 'Product Shipping Type' => 'shipping_type',
1363 'Is Autocomplete' => 'digital_auto_complete',
1364 'Price name' => 'price_name',
1365 'Price type' => 'price_type',
1366 'Setup Fee' => 'setup_fee',
1367 'Setup Fee name' => 'setup_fee_name',
1368 'Setup Fee price' => 'setup_fee_price',
1369 'Setup Fee type' => 'setup_fee_type',
1370 'Trial' => 'trial',
1371 'Trial days' => 'trial_days',
1372 'Expire' => 'expire',
1373 'Expire days' => 'expire_days',
1374 'Payment duration' => 'payment_duration',
1375 'Payment duration type' => 'payment_duration_type',
1376 'Meta Data' => 'meta_data',
1377 ];
1378 $billing_address_key_mapping = [
1379 'Billing First Name' => 'billing_first_name',
1380 'Billing Last Name' => 'billing_last_name',
1381 'Billing Company' => 'billing_company',
1382 'Billing Address 1' => 'billing_address_1',
1383 'Billing Address 2' => 'billing_address_2',
1384 'Billing City' => 'billing_city',
1385 'Billing State' => 'billing_state',
1386 'Billing Postcode' => 'billing_postcode',
1387 'Billing Country' => 'billing_country',
1388 'Billing Email' => 'billing_email',
1389 'Billing Phone' => 'billing_phone',
1390 ];
1391 $shipping_address_key_mapping = [
1392 'Shipping First Name' => 'shipping_first_name',
1393 'Shipping Last Name' => 'shipping_last_name',
1394 'Shipping Company' => 'shipping_company',
1395 'Shipping Address 1' => 'shipping_address_1',
1396 'Shipping Address 2' => 'shipping_address_2',
1397 'Shipping City' => 'shipping_city',
1398 'Shipping State' => 'shipping_state',
1399 'Shipping Postcode' => 'shipping_postcode',
1400 'Shipping Country' => 'shipping_country',
1401 'Shipping Email' => 'shipping_email',
1402 'Shipping Phone' => 'shipping_phone',
1403 ];
1404 $coupon_key_mapping = [
1405 'Coupon Code' => 'code',
1406 'Coupon Amount' => 'discount',
1407 'Coupon Amount (Tax)' => 'discount_tax',
1408 ];
1409 $tax_key_mapping = [
1410 'Tax Name' => 'name',
1411 'Tax Label' => 'label',
1412 'Tax Amount' => 'tax_total',
1413 'Tax Rate(%)' => 'rate_percent',
1414 'Shipping Tax Amount' => 'shipping_tax_total',
1415 ];
1416 $shipping_key_mapping = [
1417 'Shipping Method' => 'name',
1418 'Shipping Method Title' => 'method_title',
1419 'Amount' => 'total',
1420 'Amount (Tax)' => 'total_tax',
1421 'Tax Status' => 'tax_status',
1422 ];
1423 $fee_key_mapping = [
1424 'Fee Name' => 'name',
1425 'Fee Amount' => 'amount',
1426 'Fee Amount (Tax)' => 'total_tax',
1427 'Tax Status' => 'tax_status',
1428 ];
1429
1430 $subscriptions = [];
1431 $current_section_header = 'subscription';
1432 $header_row = [];
1433 $recent_subscription = null;
1434 while ( ( $row = fgetcsv( $fp ) ) !== false ) {
1435 if ( empty( $row ) || ( 1 === count( array_unique( $row ) ) && empty( $row[0] ) ) ) {
1436 continue;
1437 }
1438
1439 if ( ( in_array( 'Subscription Key', $row, true ) && in_array( 'Subscription Status', $row, true ) ) || in_array( '# Subscription Entity', $row, true ) ) {
1440 $current_section_header = 'subscription';
1441 $header_row = array_keys( $subscription_key_mapping );
1442 continue;
1443 } elseif ( in_array( 'Item Name', $row, true ) ) {
1444 $current_section_header = 'order_item';
1445 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 24 ) );
1446 continue;
1447 } elseif ( in_array( 'Billing First Name', $row, true ) && in_array( 'Billing Last Name', $row, true ) ) {
1448 $current_section_header = 'billing_address';
1449 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 11 ) );
1450 continue;
1451 } elseif ( in_array( 'Shipping First Name', $row, true ) && in_array( 'Shipping Last Name', $row, true ) ) {
1452 $current_section_header = 'shipping_address';
1453 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 11 ) );
1454 continue;
1455 } elseif ( in_array( 'Coupon Code', $row, true ) && in_array( 'Coupon Amount', $row, true ) ) {
1456 $current_section_header = 'coupon';
1457 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 3 ) );
1458 continue;
1459 } elseif ( in_array( 'Tax Name', $row, true ) && in_array( 'Tax Label', $row, true ) ) {
1460 $current_section_header = 'tax';
1461 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 5 ) );
1462 continue;
1463 } elseif ( in_array( 'Shipping Method', $row, true ) && in_array( 'Shipping Method Title', $row, true ) ) {
1464 $current_section_header = 'shipping';
1465 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 5 ) );
1466 continue;
1467 } elseif ( in_array( 'Fee Name', $row, true ) && in_array( 'Fee Amount', $row, true ) ) {
1468 $current_section_header = 'fee';
1469 $header_row = array_map( 'sanitize_text_field', array_slice( $row, 0, 4 ) );
1470 continue;
1471 }
1472
1473 if ( 'subscription' === $current_section_header ) {
1474 $recent_subscription = $row[ array_search( 'Subscription Key', $header_row, true ) ];
1475 $subscription_data = array_slice( $row, 0, count( $subscription_key_mapping ) );
1476 $subscription_data = array_map( function ( $value, $key ) {
1477 return 2 === $key ? sanitize_email( $value ) : sanitize_text_field( $value );
1478 }, $subscription_data, array_keys( $subscription_data ) );
1479
1480 $subscriptions[ $recent_subscription ] = Helper::rename_array_keys(
1481 array_combine( $header_row, $subscription_data ),
1482 $subscription_key_mapping
1483 );
1484 } elseif ( 'order_item' === $current_section_header ) {
1485 $subscriptions[ $recent_subscription ]['order_items'][] = Helper::rename_array_keys(
1486 array_combine(
1487 $header_row,
1488 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $order_item_key_mapping ) ) )
1489 ),
1490 $order_item_key_mapping
1491 );
1492 } elseif ( 'billing_address' === $current_section_header ) {
1493 $billing_data = array_slice( $row, 0, count( $billing_address_key_mapping ) );
1494 $billing_data = array_map( function ( $value, $key ) {
1495 return 10 === $key ? sanitize_email( $value ) : sanitize_text_field( $value );
1496 }, $billing_data, array_keys( $billing_data ) );
1497 $subscriptions[ $recent_subscription ]['billing_address'][] = Helper::rename_array_keys(
1498 array_combine( $header_row, $billing_data ),
1499 $billing_address_key_mapping
1500 );
1501 } elseif ( 'shipping_address' === $current_section_header ) {
1502 $shipping_data = array_slice( $row, 0, count( $shipping_address_key_mapping ) );
1503 $shipping_data = array_map( function ( $value, $key ) {
1504 return 10 === $key ? sanitize_email( $value ) : sanitize_text_field( $value );
1505 }, $shipping_data, array_keys( $shipping_data ) );
1506 $subscriptions[ $recent_subscription ]['shipping_address'][] = Helper::rename_array_keys(
1507 array_combine( $header_row, $shipping_data ),
1508 $shipping_address_key_mapping
1509 );
1510 } elseif ( 'coupon' === $current_section_header ) {
1511 $subscriptions[ $recent_subscription ]['coupons'][] = Helper::rename_array_keys(
1512 array_combine(
1513 $header_row,
1514 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $coupon_key_mapping ) ) )
1515 ),
1516 $coupon_key_mapping
1517 );
1518 } elseif ( 'tax' === $current_section_header ) {
1519 $subscriptions[ $recent_subscription ]['taxes'][] = Helper::rename_array_keys(
1520 array_combine(
1521 $header_row,
1522 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $tax_key_mapping ) ) )
1523 ),
1524 $tax_key_mapping
1525 );
1526 } elseif ( 'shipping' === $current_section_header ) {
1527 $subscriptions[ $recent_subscription ]['shipping'][] = Helper::rename_array_keys(
1528 array_combine(
1529 $header_row,
1530 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $shipping_key_mapping ) ) )
1531 ),
1532 $shipping_key_mapping
1533 );
1534 } elseif ( 'fee' === $current_section_header ) {
1535 $subscriptions[ $recent_subscription ]['fees'][] = Helper::rename_array_keys(
1536 array_combine(
1537 $header_row,
1538 array_map( 'sanitize_text_field', array_slice( $row, 0, count( $fee_key_mapping ) ) )
1539 ),
1540 $fee_key_mapping
1541 );
1542 }
1543 }
1544 fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1545 wp_delete_file( $filepath );
1546
1547 $expire = 60 * 60 * 4;
1548 $chunked_subscriptions = array_chunk( $subscriptions, 30, true );
1549 $filename = $this->get_filename_without_extension( $filename );
1550 set_transient( 'storeengine_csv_subscriptions_' . $filename . '_count', count( $subscriptions ), $expire );
1551 foreach ( $chunked_subscriptions as $index => $chunk ) {
1552 set_transient( 'storeengine_csv_subscriptions_' . $filename . '_' . $index, $chunk, $expire );
1553 }
1554
1555 self::$sse->emitEvent( [
1556 'type' => 'chunk-completed',
1557 'total' => count( $chunked_subscriptions ),
1558 ], true );
1559 }
1560
1561 private function get_label_value( string $slug ): ?array {
1562 $data = explode( '::', $slug );
1563 if ( 2 === count( $data ) ) {
1564 $taxonomy = $data[0];
1565 $term = $data[1];
1566 $term_id = term_exists( $term, $taxonomy );
1567 if ( ! $term_id ) {
1568 return null;
1569 }
1570
1571 return [
1572 'label' => "$term - $taxonomy",
1573 'value' => "tax-$term_id--single-$taxonomy",
1574 ];
1575 }
1576
1577 $posts = get_posts( [
1578 'name' => $slug,
1579 'post_type' => 'any',
1580 'numberposts' => 1,
1581 ] );
1582 $post = empty( $posts ) ? null : $posts[0];
1583 if ( ! $post ) {
1584 return null;
1585 }
1586
1587 return [
1588 'label' => "$post->post_title - $post->post_type",
1589 'value' => "post-$post->ID-|",
1590 ];
1591 }
1592
1593 public function download_sample_file( array $payload ) {
1594 $valid = \StoreEngine\Addons\Csv\Utils\Import::validate_data_type( $payload['type'] );
1595
1596 if ( is_wp_error( $valid ) ) {
1597 wp_send_json_error( $valid->get_error_message() );
1598 }
1599
1600 $filename = 'sample_' . $payload['type'] . '.csv';
1601
1602 $filepath = STOREENGINE_ROOT_DIR_PATH . 'sample-data/' . $filename;
1603
1604 if ( ! file_exists( $filepath ) ) {
1605 wp_send_json_error( __( 'Sample file not found!', 'storeengine' ) );
1606 }
1607
1608 header( 'Content-Type: application/octet-stream' );
1609 header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
1610 readfile( $filepath ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile
1611 exit;
1612 }
1613 }
1614