PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 4.6.0
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v4.6.0
4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.11.1 1.11.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 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 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.10.0 2.10.1 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.12.0 2.13.0 2.14.0 2.14.1 2.15.0 2.15.1 2.16.0 2.16.1 2.16.2 2.16.3 2.17.0 2.17.1 2.17.2 2.18.0 2.19.0 2.19.2 2.19.3 2.19.4 2.2.0 2.2.1 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.26.0 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.2 2.29.3 2.29.4 2.3.0 2.3.1 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.40.0 2.40.1 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.9.0 3.0.0 3.0.0-RC1 3.0.0-RC2 3.0.0-beta1 3.0.0-beta2 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.10.0 3.10.1 3.11.0 3.12.0 3.13.0 3.13.1 3.13.2 3.13.3 3.13.4 3.14.0 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.16.6 3.16.7 3.16.8 3.17.0 3.17.1 3.17.2 3.17.3 3.17.4 3.17.5 3.17.6 3.18.0 3.19.0 3.19.1 3.19.2 3.2.0 3.2.1 3.2.2 3.20.0 3.20.1 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 4.0.0 4.0.1 4.0.2 4.0.3 trunk 4.1.0 0.2.19.1 4.1.1 1.0.0 4.2.0 1.0.1 1.0.2
surecart / app / src / Controllers / Admin / Products / ProductsController.php
surecart / app / src / Controllers / Admin / Products Last commit date
ProductScriptsController.php 1 week ago ProductsController.php 6 days ago ProductsListTable.php 6 days ago
ProductsController.php
666 lines
1 <?php
2
3 namespace SureCart\Controllers\Admin\Products;
4
5 use SureCart\Controllers\Admin\AdminController;
6 use SureCart\Controllers\Admin\RendersEnhancedAdminView;
7 use SureCart\Controllers\Admin\Products\ProductsListTable;
8 use SureCart\Models\Product;
9 use SureCart\Models\ImportRow;
10 use SureCart\Background\BulkActionService;
11 use SureCart\Sync\ImportState;
12
13 /**
14 * Handles product admin requests.
15 */
16 class ProductsController extends AdminController {
17 use RendersEnhancedAdminView;
18
19 /**
20 * WooCommerce import state tracker.
21 *
22 * @var ImportState
23 */
24 private $woo_import_state;
25
26 /**
27 * Constructor.
28 *
29 * @param ImportState $woo_import_state Import state for WooCommerce runs.
30 */
31 public function __construct( ImportState $woo_import_state ) {
32 $this->woo_import_state = $woo_import_state;
33 }
34
35 /** @var string */
36 protected $page_slug = 'sc-products';
37
38 /**
39 * Key passed to \SureCart::getUrl()->index().
40 *
41 * @var string
42 */
43 protected $url_key = 'products';
44
45 /**
46 * View path prefix (no trailing slash).
47 *
48 * @var string
49 */
50 protected $view_prefix = 'admin/products';
51
52 /** @var string */
53 protected $scripts_controller_class = ProductScriptsController::class;
54
55 /** @var string */
56 protected $list_table_class = ProductsListTable::class;
57
58 /** @var string */
59 protected $bulk_delete_view_key = 'products';
60
61 /**
62 * Bulk-action name used for the async delete pipeline + admin notice.
63 * Bundles are `sc_product` posts too, so they share this processor.
64 *
65 * @var string
66 */
67 protected $bulk_action_name = 'delete_products';
68
69 /**
70 * Index breadcrumb shape used by withHeader().
71 *
72 * @return array
73 */
74 protected function indexBreadcrumb() {
75 return array(
76 'products' => array(
77 'title' => __( 'Products', 'surecart' ),
78 ),
79 );
80 }
81
82 /**
83 * Status-query → notice message map for the index page.
84 *
85 * @return array
86 */
87 protected function indexNotices() {
88 return array(
89 'sync_success' => __( 'Product synced successfully.', 'surecart' ),
90 'archived' => __( 'Product archived.', 'surecart' ),
91 'unarchived' => __( 'Product unarchived.', 'surecart' ),
92 'duplicated' => __( 'Product duplicated successfully.', 'surecart' ),
93 );
94 }
95
96 /**
97 * Admin-bar "View {entity}" link on the edit screen.
98 *
99 * @return array
100 */
101 protected function adminBarViewEntry() {
102 return array(
103 'id' => 'view-product-page',
104 'title' => __( 'View Product', 'surecart' ),
105 );
106 }
107
108 /**
109 * Message when bulk-delete is opened with no IDs selected.
110 *
111 * @return string
112 */
113 protected function bulkDeleteEmptyMessage() {
114 return __( 'No products selected. Please choose at least one product to delete.', 'surecart' );
115 }
116
117 /**
118 * Message when bulk-delete targets ids that no longer exist.
119 *
120 * @param int $count Number of ids requested for deletion.
121 *
122 * @return string
123 */
124 protected function bulkDeleteAlreadyDeletedMessage( $count ) {
125 return _n(
126 'This product has already been deleted.',
127 'These products have already been deleted.',
128 $count,
129 'surecart'
130 );
131 }
132
133 /**
134 * Header title on the confirm-bulk-delete screen.
135 *
136 * @param int $count Number of items being deleted.
137 *
138 * @return string
139 */
140 protected function bulkDeleteHeaderTitle( $count ) {
141 return _n( 'Delete Product', 'Delete Products', $count, 'surecart' );
142 }
143
144 /**
145 * Products index.
146 */
147 protected function renderWpListView() {
148 // instantiate the bulk actions service.
149 $bulk_action_service = new BulkActionService();
150 $bulk_action_service->bootstrap();
151
152 // instantiate the list table.
153 $table_class = $this->list_table_class;
154 $table = new $table_class( $bulk_action_service );
155 $table->prepare_items();
156
157 // add header.
158 $this->withHeader(
159 array(
160 'breadcrumbs' => $this->indexBreadcrumb(),
161 'suffix' => isset( $_GET['debug'] ) ? $this->syncDropdown() : null, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
162 'enhanced_view_promo' => $this->currentAdminPageUrl(),
163 ),
164 );
165
166 // add notices.
167 $this->withNotices( $this->indexNotices() );
168
169 // return view.
170 return \SureCart::view( $this->view_prefix . '/index' )->with( [ 'table' => $table ] );
171 }
172
173 /**
174 * Render the SPA view for products.
175 */
176 protected function renderSpaView() {
177 $this->enqueueSpaScripts( $this->scripts_controller_class );
178
179 $bulk_action_service = new BulkActionService();
180 $bulk_action_service->bootstrap();
181
182 $bulk_action_name = $this->bulk_action_name;
183 add_action(
184 'admin_notices',
185 function () use ( $bulk_action_service, $bulk_action_name ) {
186 $bulk_action_service->showBulkActionAdminNotice( $bulk_action_name );
187 }
188 );
189
190 // Derive the SPA-shell breadcrumb from the same source the legacy
191 // header uses, so subclasses only override indexBreadcrumb() once.
192 $breadcrumb = $this->indexBreadcrumb();
193 $key = array_key_first( $breadcrumb );
194
195 return $this->renderSpaShell( $this->view_prefix . '/spa', $key, $breadcrumb[ $key ]['title'] );
196 }
197
198 /**
199 * Sync dropdown.
200 */
201 public function syncDropdown() {
202 ob_start();
203 ?>
204 <sc-dropdown>
205 <sc-button slot="trigger" type="text" circle>
206 <sc-icon name="more-horizontal" style="font-size: 20px"></sc-icon>
207 </sc-button>
208 <sc-menu>
209 <sc-menu-item href="<?php echo esc_url( \SureCart::getUrl()->syncAll( 'products' ) ); ?>">
210 <sc-icon slot="prefix" name="refresh-cw"></sc-icon>
211 <?php esc_html_e( 'Sync Products', 'surecart' ); ?>
212 </sc-menu-item>
213 </sc-menu>
214 </sc-dropdown>
215 <?php
216 return ob_get_clean();
217 }
218
219 /**
220 * Confirm Bulk Delete.
221 */
222 public function confirmBulkDelete() {
223 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only view; nonce is enforced on the downstream POST to bulkDelete().
224 $raw_ids = isset( $_REQUEST['bulk_action_product_ids'] ) ? wp_unslash( $_REQUEST['bulk_action_product_ids'] ) : [];
225 $product_ids = is_array( $raw_ids ) ? array_map( 'sanitize_text_field', $raw_ids ) : [];
226
227 // find the products queued for bulk deletion.
228 if ( empty( $product_ids ) ) {
229 wp_die(
230 sprintf(
231 '%s <a href="%s">%s</a>',
232 esc_html( $this->bulkDeleteEmptyMessage() ),
233 esc_url( admin_url( 'admin.php?page=' . $this->page_slug ) ),
234 esc_html__( 'Go Back', 'surecart' )
235 )
236 );
237 }
238
239 if ( $this->isEnhancedAdminViewsEnabled() ) {
240 return $this->renderSpaView();
241 }
242
243 $products = Product::where(
244 [
245 'ids' => $product_ids,
246 ]
247 )->get();
248
249 // handle empty.
250 if ( empty( $products ) ) {
251 wp_die( esc_html( $this->bulkDeleteAlreadyDeletedMessage( count( $product_ids ) ) ) );
252 }
253
254 // handle error.
255 if ( is_wp_error( $products ) ) {
256 wp_die( implode( ' ', array_map( 'esc_html', $products->get_error_messages() ) ) );
257 }
258
259 // add header.
260 $this->withHeader(
261 [
262 'delete' => [
263 'title' => $this->bulkDeleteHeaderTitle( count( $products ) ),
264 ],
265 ],
266 );
267
268 // return view.
269 return \SureCart::view( $this->view_prefix . '/confirm-bulk-delete' )
270 ->with( [ $this->bulk_delete_view_key => $products ] );
271 }
272
273 /**
274 * Bulk Delete.
275 */
276 public function bulkDelete() {
277 // Route middleware `nonce:bulk_delete_nonce` has already verified the
278 // nonce; we only need to coerce the payload to a sanitized array here.
279 $product_ids = isset( $_REQUEST['bulk_action_product_ids'] ) && is_array( $_REQUEST['bulk_action_product_ids'] )
280 ? array_map( 'sanitize_text_field', wp_unslash( $_REQUEST['bulk_action_product_ids'] ) )
281 : [];
282
283 if ( empty( $product_ids ) ) {
284 return \SureCart::redirect()->to( esc_url_raw( admin_url( 'admin.php?page=' . $this->page_slug ) ) );
285 }
286
287 // get all posts where the sc_id meta key is in the product_ids using wp_query.
288 $query = new \WP_Query(
289 [
290 'post_type' => 'sc_product',
291 'posts_per_page' => -1,
292 'meta_query' => [
293 [
294 'key' => 'sc_id',
295 'value' => $product_ids,
296 'compare' => 'IN',
297 ],
298 ],
299 ]
300 );
301
302 // handle error.
303 if ( is_wp_error( $query ) ) {
304 wp_die( implode( ' ', array_map( 'esc_html', $query->get_error_messages() ) ) );
305 }
306
307 // delete the posts.
308 foreach ( $query->posts as $post ) {
309 wp_delete_post( $post->ID, true );
310 }
311
312 // create bulk action.
313 $action = \SureCart::bulkAction()->createBulkAction(
314 $this->bulk_action_name,
315 $product_ids
316 );
317
318 // handle error.
319 if ( is_wp_error( $action ) ) {
320 wp_die( implode( ' ', array_map( 'esc_html', $action->get_error_messages() ) ) );
321 }
322
323 // redirect.
324 return \SureCart::redirect()->to( esc_url_raw( admin_url( 'admin.php?page=' . $this->page_slug ) ) );
325 }
326
327 /**
328 * Edit a product.
329 *
330 * @param \SureCartCore\Requests\RequestInterface $request Request.
331 */
332 public function edit( $request ) {
333 // enqueue needed script.
334 $this->enqueueSpaScripts( $this->scripts_controller_class );
335
336 // define product.
337 $product = null;
338
339 // find the product for preloading.
340 if ( $request->query( 'id' ) ) {
341 $product = Product::find( $request->query( 'id' ) );
342
343 if ( is_wp_error( $product ) ) {
344 wp_die( implode( ' ', array_map( 'esc_html', $product->get_error_messages() ) ) );
345 }
346 }
347
348 // preload paths.
349 if ( ! empty( $product ) ) {
350 $gallery_paths = [];
351 $gallery = $product->gallery_ids ?? [];
352 foreach ( $gallery as $gallery_item ) {
353 $id = is_numeric( $gallery_item ) ? (int) $gallery_item : intval( ( (object) $gallery_item )->id ?? 0 );
354
355 if ( $id > 0 ) {
356 $gallery_paths[] = '/wp/v2/media/' . $id . '?context=edit';
357 }
358 }
359 $taxonomies = array_diff( get_object_taxonomies( 'sc_product' ), array( 'sc_account', 'sc_collection' ) );
360
361 if ( ! empty( $taxonomies ) ) {
362 $taxonomy_paths = [];
363 foreach ( $taxonomies as $taxonomy ) {
364 $taxonomy_paths[] = '/wp/v2/taxonomies/' . $taxonomy . '?context=edit';
365 $taxonomy_paths[] = '/wp/v2/' . $taxonomy;
366 }
367 }
368
369 $this->preloadPaths(
370 array_merge(
371 [
372 [ '/wp/v2/templates', 'OPTIONS' ],
373 '/wp/v2/settings',
374 '/wp/v2/types/wp_template?context=edit',
375 '/wp/v2/types/wp_template-part?context=edit',
376 '/wp/v2/templates?context=edit&per_page=-1',
377 '/wp/v2/template-parts?context=edit&per_page=-1',
378 '/wp/v2/users/me',
379 '/wp/v2/types?context=view',
380 '/wp/v2/types?context=edit',
381 '/wp/v2/templates/' . $product->template_id . '?context=edit',
382 '/wp/v2/template-parts/' . $product->template_part_id . '?context=edit',
383 '/wp/v2/taxonomies?context=view',
384 '/wp/v2/taxonomies?context=edit&per_page=100',
385 '/wp/v2/sc_product?context=edit&sc_id[0]=' . $product->id . '&per_page=1&_locale=user',
386 '/surecart/v1/products/' . $product->id . '?context=edit',
387 '/surecart/v1/integrations?context=edit&model_ids[0]=' . $product->id . '&per_page=50',
388 '/surecart/v1/integration_providers?context=edit',
389 '/surecart/v1/integration_provider_items?context=edit',
390 ],
391 $gallery_paths,
392 $taxonomy_paths ?? []
393 )
394 );
395 }
396
397 // add product link.
398 if ( ! empty( $product ) ) {
399 $admin_bar_entry = $this->adminBarViewEntry();
400 add_action(
401 'admin_bar_menu',
402 function ( $wp_admin_bar ) use ( $product, $admin_bar_entry ) {
403 $wp_admin_bar->add_node(
404 [
405 'id' => $admin_bar_entry['id'],
406 'title' => $admin_bar_entry['title'],
407 'href' => esc_url( $product->permalink ?? '#' ),
408 'meta' => [
409 'class' => empty( $product->permalink ) ? 'hidden' : '',
410 ],
411 ]
412 );
413 },
414 99
415 );
416 }
417
418 return $this->renderSpaShell( $this->view_prefix . '/spa' );
419 }
420
421 /**
422 * Change the archived attribute in the model
423 *
424 * @param \SureCartCore\Requests\RequestInterface $request Request.
425 *
426 * @return \SureCartCore\Responses\RedirectResponse
427 */
428 public function toggleArchive( $request ) {
429 $product = Product::find( $request->query( 'id' ) );
430
431 if ( is_wp_error( $product ) ) {
432 wp_die( implode( ' ', array_map( 'esc_html', $product->get_error_messages() ) ) );
433 }
434
435 $updated = $product->update(
436 [
437 'archived' => ! (bool) $product->archived,
438 ]
439 );
440
441 if ( is_wp_error( $updated ) ) {
442 wp_die( implode( ' ', array_map( 'esc_html', $updated->get_error_messages() ) ) );
443 }
444
445 return \SureCart::redirect()->to(
446 esc_url_raw(
447 add_query_arg(
448 $updated->archived ? [ 'archived' => 1 ] : [ 'unarchived' => 1 ],
449 \SureCart::getUrl()->index( $this->url_key )
450 )
451 )
452 );
453 }
454
455 /**
456 * Start product sync.
457 *
458 * @return \SureCartCore\Responses\RedirectResponse
459 */
460 public function syncAll() {
461 // dispatch the sync job.
462 \SureCart::sync()->products()->dispatch();
463
464 // redirect to products page.
465 return \SureCart::redirect()->to( esc_url_raw( \SureCart::getUrl()->index( $this->url_key ) ) );
466 }
467
468 /**
469 * Import results page.
470 *
471 * @param \SureCartCore\Requests\RequestInterface $request Request.
472 *
473 * @return \SureCartCore\View
474 */
475 public function importResults( $request ) {
476 // add header.
477 $this->withHeader(
478 [
479 'breadcrumbs' => [
480 'products' => [
481 'title' => __( 'Products', 'surecart' ),
482 'href' => \SureCart::getUrl()->index( 'products' ),
483 ],
484 'import_results' => [
485 'title' => __( 'Import Results', 'surecart' ),
486 ],
487 ],
488 ],
489 );
490
491 // Parse import IDs from request (comma-separated), with fallback to legacy singular param.
492 $import_ids_raw = $request->query( 'import_ids' );
493 if ( empty( $import_ids_raw ) ) {
494 $import_ids_raw = $request->query( 'import_id' );
495 }
496
497 if ( empty( $import_ids_raw ) ) {
498 // Check if this is an all-skipped import.
499 $all_skipped_session_id = $this->woo_import_state->getAllSkippedSessionId();
500
501 // Fallback: if option was already cleaned up, check URL param (e.g. page refresh).
502 if ( ! $all_skipped_session_id ) {
503 $raw_session_id = $request->query( 'session_id' );
504 $all_skipped_session_id = $raw_session_id ? substr( sanitize_key( $raw_session_id ), 0, 36 ) : '';
505 }
506
507 if ( $all_skipped_session_id ) {
508 // Fetch skipped products via session ID.
509 $skipped_products = $this->woo_import_state->getSkippedItemsBySession( $all_skipped_session_id );
510
511 // Clean up completion notice options now that the user has viewed results.
512 $this->woo_import_state->reset();
513
514 return \SureCart::view( 'admin/products/import-results' )->with(
515 [
516 'succeeded_count' => 0,
517 'failed_rows' => [],
518 'skipped_products' => $skipped_products,
519 'all_skipped' => true,
520 'results_capped' => false,
521 ]
522 );
523 }
524
525 // No import IDs and no skipped products - show empty state.
526 return \SureCart::view( 'admin/products/import-results' )->with(
527 [
528 'succeeded_count' => 0,
529 'failed_rows' => [],
530 'skipped_products' => [],
531 'all_skipped' => false,
532 'results_capped' => false,
533 ]
534 );
535 }
536
537 // Sanitize, split into array, and cap at 50 IDs to prevent abuse.
538 $import_ids = array_slice(
539 array_filter( array_map( 'sanitize_key', explode( ',', $import_ids_raw ) ) ),
540 0,
541 50
542 );
543
544 // Parse session_id from query (for skipped products lookup).
545 $raw = $request->query( 'session_id' );
546 $session_id = $raw ? substr( sanitize_key( $raw ), 0, 36 ) : '';
547
548 // Fallback: use current session if available (for backward compatibility).
549 if ( ! $session_id ) {
550 $session_id = $this->woo_import_state->getSessionId();
551 }
552
553 // Fetch all import rows (capped at 50 pages to prevent timeouts).
554 $succeeded_count = 0;
555 $failed_rows = [];
556 $page = 1;
557 $per_page = 100;
558 $max_pages = 50;
559 $has_next_page = false;
560
561 do {
562 $collection = ImportRow::where( [ 'import_ids' => $import_ids ] )
563 ->paginate(
564 [
565 'page' => $page,
566 'per_page' => $per_page,
567 ]
568 );
569
570 // Handle API errors gracefully.
571 if ( is_wp_error( $collection ) ) {
572 break;
573 }
574
575 foreach ( ( $collection->data ?? [] ) as $row ) {
576 if ( 'succeeded' === ( $row->status ?? '' ) ) {
577 ++$succeeded_count;
578 } else {
579 $import_data = $row->import_data ?? null;
580 $failed_rows[] = [
581 'name' => is_object( $import_data ) ? ( $import_data->name ?? __( 'Unknown', 'surecart' ) ) : __( 'Unknown', 'surecart' ),
582 'reason' => $row->failure_reason ?? __( 'Unknown error', 'surecart' ),
583 ];
584 }
585 }
586
587 $has_next_page = $collection->hasNextPage();
588 ++$page;
589 } while ( $has_next_page && $page <= $max_pages );
590
591 // Flag if results were capped (more rows exist than the pagination limit allows).
592 $results_capped = ( $page > $max_pages && $has_next_page );
593
594 // Fetch skipped products from transient.
595 $skipped_products = [];
596 if ( $session_id ) {
597 $skipped_products = $this->woo_import_state->getSkippedItemsBySession( $session_id );
598 }
599
600 // Clean up completion notice options now that the user has viewed results.
601 // The results page uses URL query params, so these options are no longer needed.
602 $this->woo_import_state->reset();
603
604 return \SureCart::view( 'admin/products/import-results' )->with(
605 [
606 'succeeded_count' => $succeeded_count,
607 'failed_rows' => $failed_rows,
608 'skipped_products' => $skipped_products,
609 'all_skipped' => false,
610 'results_capped' => $results_capped,
611 ]
612 );
613 }
614
615 /**
616 * Start product sync.
617 *
618 * @param \SureCartCore\Requests\RequestInterface $request Request.
619 *
620 * @return \SureCartCore\Responses\RedirectResponse
621 */
622 public function sync( $request ) {
623 $product = Product::sync( $request->query( 'id' ) );
624
625 if ( is_wp_error( $product ) ) {
626 wp_die( implode( ' ', array_map( 'esc_html', $product->get_error_messages() ) ) );
627 }
628
629 // redirect to products page.
630 return \SureCart::redirect()->to(
631 esc_url_raw(
632 add_query_arg(
633 [ 'sync_success' => true ],
634 \SureCart::getUrl()->index( $this->url_key )
635 )
636 )
637 );
638 }
639
640 /**
641 * Duplicate a product.
642 *
643 * @param \SureCartCore\Requests\RequestInterface $request Request.
644 *
645 * @return \SureCartCore\Responses\RedirectResponse
646 */
647 public function duplicate( $request ) {
648 $duplicated = Product::duplicate( $request->query( 'id' ) );
649
650 if ( is_wp_error( $duplicated ) ) {
651 wp_die( implode( ' ', array_map( 'esc_html', $duplicated->get_error_messages() ) ) );
652 }
653
654 return \SureCart::redirect()->to(
655 esc_url_raw(
656 add_query_arg(
657 [
658 'duplicated' => true,
659 ],
660 \SureCart::getUrl()->index( $this->url_key )
661 )
662 )
663 );
664 }
665 }
666