PluginProbe
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 3.4.1
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v3.4.1
4.7.2 4.7.1 4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 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 All 281 releases
surecart / app / src / Sync / PostSyncService.php
PostSyncService.php
429 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SureCart\Sync;
4
5 use SureCart\Models\Concerns\Facade;
6
7 use SureCart\Models\VariantOptionValue;
8 use SureCart\Support\Currency;
9
10 /**
11 * This class syncs product records to WordPress posts.
12 */
13 class PostSyncService {
14 use Facade;
15
16 /**
17 * The product model.
18 *
19 * @var \SureCart\Models\Product
20 */
21 protected $product;
22
23 /**
24 * The post.
25 *
26 * @var \WP_Post
27 */
28 protected $post;
29
30 /**
31 * The post type.
32 *
33 * @var string
34 */
35 protected $post_type = 'sc_product';
36
37 /**
38 * The excluded meta keys.
39 *
40 * @var array
41 */
42 protected $excluded_meta_keys = [
43 'gallery',
44 'gallery_ids',
45 'meta_description',
46 'page_title',
47 'wp_buy_link_coupon_field_disabled',
48 'wp_buy_link_custom_thankyou_page',
49 'wp_buy_link_custom_thankyou_page_url',
50 'wp_buy_link_enabled',
51 'wp_buy_link_logo_disabled',
52 'wp_buy_link_product_description_disabled',
53 'wp_buy_link_product_image_disabled',
54 'wp_buy_link_success_page_enabled',
55 'wp_buy_link_success_page_url',
56 'wp_buy_link_terms_disabled',
57 'wp_buy_link_test_mode_enabled',
58 'wp_created_by',
59 'wp_template_part_id',
60 'wp_template_id',
61 ];
62
63 /**
64 * Find the post from the model.
65 *
66 * @param string $model_id The model id.
67 *
68 * @return \WP_Post|\WP_Error|null
69 */
70 protected function findByModelId( $model_id ) {
71 // if we don't have a model id, return null.
72 if ( empty( $model_id ) ) {
73 return null;
74 }
75
76 // query the post.
77 $query = new \WP_Query(
78 array(
79 'post_type' => $this->post_type,
80 'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash', 'sc_archived', 'future' ),
81 'posts_per_page' => 1,
82 'no_found_rows' => true,
83 'suppress_filters' => true,
84 'meta_query' => array(
85 array(
86 'key' => 'sc_id',
87 'value' => $model_id, // query by model id.
88 ),
89 ),
90 )
91 );
92
93 // handle error.
94 if ( is_wp_error( $query ) ) {
95 return $query;
96 }
97
98 // get the post.
99 $post = $query->posts[0] ?? null;
100
101 // return the post.
102 return apply_filters( "surecart_get_{$this->post_type}_post", $post, $model_id, $this );
103 }
104
105 /**
106 * Sync the model with the post.
107 *
108 * @param \SureCart\Models\Model $model The model.
109 *
110 * @return \WP_Post|\WP_Error
111 */
112 protected function sync( \SureCart\Models\Model $model ) {
113 $this->post = $this->findByModelId( $model->id );
114
115 if ( is_wp_error( $this->post ) ) {
116 return $this->post;
117 }
118
119 return empty( $this->post->ID ) ? $this->create( $model ) : $this->update( $model );
120 }
121
122 /**
123 * Delete the synced post.
124 *
125 * @param string $id The model id.
126 *
127 * @return \WP_Post|\WP_Error|false|null
128 */
129 protected function delete( string $id ) {
130 $this->post = $this->findByModelId( $id );
131
132 if ( is_wp_error( $this->post ) ) {
133 return $this->post;
134 }
135
136 // force delete post.
137 $deleted = wp_delete_post( $this->post->ID, true );
138 if ( is_wp_error( $deleted ) ) {
139 return $deleted;
140 }
141
142 // delete variant option values where post_id is the post id.
143 $deleted_option = VariantOptionValue::where( 'product_id', $id )->delete();
144 if ( is_wp_error( $deleted_option ) ) {
145 return $deleted_option;
146 }
147
148 return $deleted;
149 }
150
151 /**
152 * Get the schema map.
153 *
154 * @param \SureCart\Models\Model $model The model.
155 *
156 * @return array
157 */
158 protected function getSchemaMap( \SureCart\Models\Model $model ) {
159 $base_amount = ! empty( $model->prices->data[0]->amount ) ? $model->prices->data[0]->amount : 0;
160
161 // Filter metadata using array_filter with a callback.
162 $metadata = array_filter(
163 (array) $model->metadata,
164 function ( $key ) {
165 $excluded = apply_filters( 'surecart_excluded_post_meta_keys', $this->excluded_meta_keys );
166 return ! in_array( $key, $excluded, true );
167 },
168 ARRAY_FILTER_USE_KEY // pass just the key to the callback.
169 );
170
171 return array(
172 'post_title' => $model->name,
173 'post_type' => $this->post_type,
174 'post_name' => $model->slug,
175 'menu_order' => $model->position ?? 0,
176 'post_excerpt' => $model->description ?? '',
177 'post_date' => ( new \DateTime() )->setTimestamp( $model->cataloged_at )->setTimezone( new \DateTimeZone( wp_timezone_string() ) )->format( 'Y-m-d H:i:s' ),
178 'post_date_gmt' => date_i18n( 'Y-m-d H:i:s', $model->cataloged_at, true ),
179 'post_modified' => ( new \DateTime() )->setTimestamp( $model->updated_at )->setTimezone( new \DateTimeZone( wp_timezone_string() ) )->format( 'Y-m-d H:i:s' ),
180 'post_modified_gmt' => date_i18n( 'Y-m-d H:i:s', $model->updated_at, true ),
181 'post_status' => $this->getPostStatusFromModel( $model ),
182 'meta_input' => array_merge(
183 $metadata,
184 array(
185 'sc_id' => $model->id,
186 'product' => $model->toArray(),
187 'min_price_amount' => ! empty( $model->metrics->min_price_amount ) ? Currency::maybeConvertAmount( $model->metrics->min_price_amount ?? 0, $model->initial_price->currency ?? null ) : $base_amount,
188 'max_price_amount' => ! empty( $model->metrics->max_price_amount ) ? Currency::maybeConvertAmount( $model->metrics->max_price_amount ?? 0, $model->initial_price->currency ?? null ) : $base_amount,
189 'available_stock' => $model->available_stock,
190 'stock_enabled' => $model->stock_enabled,
191 'allow_out_of_stock_purchases' => $model->allow_out_of_stock_purchases,
192 'featured' => $model->featured,
193 'recurring' => $model->recurring,
194 'shipping_enabled' => $model->shipping_enabled,
195 'purchase_limit' => $model->purchase_limit,
196 'sku' => $model->sku,
197 'weight' => $model->weight,
198 'weight_unit' => $model->weight_unit,
199 'display_amount' => $model->display_amount,
200 'scratch_display_amount' => $model->scratch_display_amount,
201 'range_display_amount' => $model->range_display_amount,
202 'is_on_sale' => $model->is_on_sale,
203 )
204 ),
205 );
206 }
207
208 /**
209 * Convert the template id.
210 *
211 * This removes the template theme prefix since this is not needed.
212 *
213 * @param string $id The template id.
214 *
215 * @return string
216 */
217 protected function convertTemplateId( $id ) {
218 $parts = explode( '//', $id, 2 );
219
220 // not a FSE template.
221 if ( count( $parts ) < 2 ) {
222 return $id;
223 }
224
225 return $parts[1] ?? null;
226 }
227
228 /**
229 * Create the post.
230 *
231 * @param \SureCart\Models\Model $model The model.
232 *
233 * @return \WP_Post|\WP_Error
234 */
235 protected function create( \SureCart\Models\Model $model ) {
236 // don't do these actions as they can slow down the sync.
237 foreach ( array( 'do_pings', 'transition_post_status', 'save_post', 'pre_post_update', 'add_attachment', 'edit_attachment', 'edit_post', 'post_updated', 'wp_insert_post', 'save_post_' . $this->post_type ) as $action ) {
238 remove_all_actions( $action );
239 }
240
241 // we are importing.
242 if ( ! defined( 'WP_IMPORTING' ) ) {
243 define( 'WP_IMPORTING', true );
244 }
245
246 // insert post.
247 $props = $this->getSchemaMap( $model );
248 $post_id = wp_insert_post( wp_slash( $props ), true, false );
249
250 // handle errors.
251 if ( is_wp_error( $post_id ) ) {
252 return $post_id;
253 }
254
255 // If there is a page template, use that, otherwise use the default.
256 update_post_meta( $post_id, '_wp_page_template', $this->convertTemplateId( $model->template_id ?? '' ) );
257 update_post_meta( $post_id, '_wp_page_template_part', $this->convertTemplateId( $model->template_part_id ?? '' ) );
258
259 $this->syncCollections( $post_id, $model );
260
261 // we need to do this because tax_input checks permissions for some ungodly reason.
262 wp_set_post_terms( $post_id, \SureCart::account()->id, 'sc_account' );
263
264 $values = $this->syncVariantValues( $model, $post_id );
265 if ( is_wp_error( $values ) ) {
266 return $values;
267 }
268
269 // set the post on the model.
270 $this->post = get_post( $post_id );
271
272 return $this->post;
273 }
274
275 /**
276 * Update the post.
277 *
278 * @param \SureCart\Models\Model $model The model.
279 *
280 * @return \WP_Post|\WP_Error
281 */
282 protected function update( $model ) {
283 // find the post.
284 if ( empty( $this->post ) ) {
285 $this->post = $this->findByModelId( $model->id );
286 }
287
288 $props = $this->getSchemaMap( $model );
289
290 // if we already have a post template, don't sync. This is to prevent overwriting the template.
291 if ( ! empty( $this->post->page_template ) ) {
292 unset( $props['page_template'] );
293 }
294
295 // update the post by id.
296 $post_id = wp_update_post(
297 array_merge(
298 $props,
299 array(
300 'ID' => $this->post->ID,
301 )
302 )
303 );
304
305 if ( is_wp_error( $post_id ) ) {
306 return $post_id;
307 }
308
309 // update page template.
310 update_post_meta( $post_id, '_wp_page_template', $this->convertTemplateId( $model->template_id ?? '' ) );
311 update_post_meta( $post_id, '_wp_page_template_part', $this->convertTemplateId( $model->template_part_id ?? '' ) );
312
313 // set the collection terms.
314 $this->syncCollections( $post_id, $model );
315
316 // we need to do this because tax_input checks permissions for some ungodly reason.
317 wp_set_post_terms( $post_id, \SureCart::account()->id, 'sc_account' );
318
319 $values = $this->syncVariantValues( $model, $post_id );
320 if ( is_wp_error( $values ) ) {
321 return $values;
322 }
323
324 $this->post = get_post( $post_id );
325
326 return $this->post;
327 }
328
329 /**
330 * Sync the variant values.
331 *
332 * @param int $model_id The model id.
333 * @param int $post_id The post id.
334 *
335 * @return bool|\WP_Error
336 */
337 protected function syncVariantValues( $model, $post_id ) {
338 // delete existing.
339 VariantOptionValue::where( 'product_id', $model->id )->delete();
340
341 // create new.
342 foreach ( ( $model->variant_options->data ?? array() ) as $option ) {
343 foreach ( $option->values as $value ) {
344 $created = VariantOptionValue::create(
345 array(
346 'value' => $value,
347 'name' => $option->name,
348 'post_id' => $post_id,
349 'product_id' => $model->id,
350 )
351 );
352 if ( is_wp_error( $created ) ) {
353 return $created;
354 }
355 }
356 }
357
358 return true;
359 }
360
361 /**
362 * Sync the collections.
363 *
364 * @param int $post_id The post id.
365 * @param \SureCart\Models\Model $model The model.
366 *
367 * @return void
368 */
369 protected function syncCollections( $post_id, $model ) {
370 // sanity check.
371 if ( ! isset( $model->product_collections ) || ! isset( $model->product_collections->data ) || ! is_array( $model->product_collections->data ) ) {
372 return;
373 }
374
375 // store the terms for the post.
376 $terms = array();
377
378 // Loop through the collections.
379 foreach ( $model->product_collections->data as $collection ) {
380 // sync the collection.
381 $collection->sync();
382
383 // get term by sc_id.
384 $term = $collection->term;
385
386 // error handling.
387 if ( is_wp_error( $term ) ) {
388 error_log( $term->get_error_message() );
389 continue;
390 }
391
392 // if we don't have a term id, skip.
393 if ( ! isset( $term->term_id ) || empty( $term->term_id ) ) {
394 error_log( 'Could not sync term for collection: ' . $collection->name );
395 error_log( print_r( $term, true ) );
396 continue;
397 }
398
399 // add to terms array.
400 $terms[] = (int) $term->term_id;
401 }
402
403 // set the terms.
404 wp_set_post_terms( $post_id, $terms, 'sc_collection' );
405 }
406
407 /**
408 * Add archived to the post meta.
409 *
410 * @param \SureCart\Models\Model $model The model.
411 *
412 * @return string
413 */
414 protected function getPostStatusFromModel( \SureCart\Models\Model $model ) {
415 // if it's archived, use that.
416 if ( $model->archived ) {
417 return 'sc_archived';
418 }
419
420 // if it's draft, use that.
421 if ( 'draft' === ( $model->status ?? '' ) ) {
422 return 'draft';
423 }
424
425 // default to publish.
426 return 'publish';
427 }
428 }
429