PluginProbe ʕ •ᴥ•ʔ
WooCommerce Square / 2.2.5
WooCommerce Square v2.2.5
5.5.0 5.4.3 5.4.2 5.4.1 5.4.0 trunk 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.7.0 2.8.0 2.9.0 2.9.1 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.2.0 3.3.0 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.7.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 4.0.0 4.1.0 4.2.0 4.2.1 4.2.2 4.2.3 4.3.0 4.3.1 4.3.2 4.4.0 4.4.1 4.4.2 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.6.2 4.6.3 4.6.4 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5 4.8.6 4.8.7 4.8.8 4.9.0 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.6 4.9.7 4.9.8 4.9.9 5.0.0 5.0.1 5.1.0 5.1.1 5.1.2 5.2.0 5.3.0 5.3.1 5.3.2 5.3.3
woocommerce-square / includes / Handlers / Sync.php
woocommerce-square / includes / Handlers Last commit date
Product 6 years ago Background_Job.php 6 years ago Category.php 6 years ago Connection.php 6 years ago Email.php 6 years ago Order.php 6 years ago Product.php 5 years ago Products.php 5 years ago Sync.php 5 years ago
Sync.php
519 lines
1 <?php
2 /**
3 * WooCommerce Square
4 *
5 * This source file is subject to the GNU General Public License v3.0
6 * that is bundled with this package in the file license.txt.
7 * It is also available through the world-wide-web at this URL:
8 * http://www.gnu.org/licenses/gpl-3.0.html
9 * If you did not receive a copy of the license and are unable to
10 * obtain it through the world-wide-web, please send an email
11 * to license@woocommerce.com so we can send you a copy immediately.
12 *
13 * DISCLAIMER
14 *
15 * Do not edit or add to this file if you wish to upgrade WooCommerce Square to newer
16 * versions in the future. If you wish to customize WooCommerce Square for your
17 * needs please refer to https://docs.woocommerce.com/document/woocommerce-square/
18 *
19 * @author WooCommerce
20 * @copyright Copyright: (c) 2019, Automattic, Inc.
21 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
22 */
23
24 namespace WooCommerce\Square\Handlers;
25
26 use SkyVerge\WooCommerce\PluginFramework\v5_4_0 as Framework;
27 use WooCommerce\Square\Plugin;
28 use WooCommerce\Square\Sync\Interval_Polling;
29 use WooCommerce\Square\Sync\Records;
30
31 defined( 'ABSPATH' ) || exit;
32
33 /**
34 * Synchronization handler class
35 *
36 * @since 2.0.0
37 */
38 class Sync {
39
40
41 /** @var string key of the option that stores a timestamp when the last sync job completed */
42 private $last_synced_at_option_key = 'wc_square_last_synced_at';
43
44 /** @var string name of the Action Scheduler event name for syncing with Square */
45 private $sync_scheduled_event_name;
46
47 /** @var Plugin plugin instance */
48 private $plugin;
49
50
51 /**
52 * Constructs the class.
53 *
54 * @since 2.0.0
55 *
56 * @param Plugin $plugin
57 */
58 public function __construct( Plugin $plugin ) {
59
60 $this->plugin = $plugin;
61
62 $this->sync_scheduled_event_name = 'wc_' . $this->get_plugin()->get_id() . '_sync';
63
64 $this->add_hooks();
65 }
66
67
68 /**
69 * Adds the action & filter hooks.
70 *
71 * @since 2.0.0
72 */
73 private function add_hooks() {
74
75 // schedule the interval sync
76 add_action( 'init', array( $this, 'schedule_sync' ) );
77
78 // run the interval sync when fired by Action Scheduler
79 add_action( $this->sync_scheduled_event_name, array( $this, 'start_interval_sync' ) );
80 }
81
82
83 /**
84 * Gets the sync schedule interval in seconds.
85 *
86 * @since 2.0.0
87 *
88 * @return int
89 */
90 private function get_sync_schedule_interval() {
91
92 /**
93 * Filters the frequency with which products should be synced.
94 *
95 * @since 2.0.0
96 *
97 * @param int $interval sync interval in seconds (defaults to one hour)
98 */
99 return (int) max( MINUTE_IN_SECONDS, (int) apply_filters( 'wc_square_sync_interval', HOUR_IN_SECONDS ) );
100 }
101
102
103 /**
104 * Schedules the interval sync.
105 *
106 * @since 2.0.0
107 */
108 public function schedule_sync() {
109
110 // bail if product sync is not enabled or there hasn't been a previous sync
111 if ( $this->is_sync_in_progress() || ! $this->get_last_synced_at() || ! $this->get_plugin()->get_settings_handler()->is_connected() || ! $this->get_plugin()->get_settings_handler()->is_product_sync_enabled() ) {
112 return;
113 }
114
115 $plugin_id = $this->get_plugin()->get_id();
116 $interval = $this->get_sync_schedule_interval();
117
118 if ( false === as_next_scheduled_action( $this->sync_scheduled_event_name, array(), $plugin_id ) ) {
119 as_schedule_recurring_action( time() + $interval, $interval, $this->sync_scheduled_event_name, array(), $plugin_id );
120 }
121 }
122
123
124 /**
125 * Unschedules the interval sync.
126 *
127 * @since 2.0.0
128 */
129 public function unschedule_sync() {
130
131 as_unschedule_action( $this->sync_scheduled_event_name, array(), $this->get_plugin()->get_id() );
132 }
133
134
135 /**
136 * Performs a product import from Square.
137 *
138 * @since 2.0.0
139 *
140 * @param bool $dispatch whether the job should be immediately dispatched
141 * @param bool $update_during_import whether the store manager has ticked to update products during an import
142 * @return \stdClass|null
143 */
144 public function start_product_import( $dispatch = true, $update_during_import = false ) {
145
146 $job = $this->get_plugin()->get_background_job_handler()->create_job(
147 array(
148 'action' => 'product_import',
149 'update_products_during_import' => $update_during_import,
150 )
151 );
152
153 if ( $job ) {
154
155 if ( $dispatch ) {
156 $this->get_plugin()->get_background_job_handler()->dispatch();
157 }
158 }
159
160 return $job;
161 }
162
163
164 /**
165 * Performs a manual sync.
166 *
167 * @since 2.0.0
168 *
169 * @param bool $dispatch whether the job should be immediately dispatched
170 * @param int[] $product_ids (optional) array of product IDs to sync
171 * @return \stdClass|null
172 */
173 public function start_manual_sync( $dispatch = true, array $product_ids = array() ) {
174
175 $product_ids = empty( $product_ids ) ? Product::get_products_synced_with_square() : $product_ids;
176
177 $job = $this->get_plugin()->get_background_job_handler()->create_job(
178 array(
179 'action' => 'sync',
180 'manual' => true,
181 'product_ids' => $product_ids,
182 )
183 );
184
185 if ( $job ) {
186
187 if ( $dispatch ) {
188 $this->get_plugin()->get_background_job_handler()->dispatch();
189 }
190 }
191
192 return $job;
193 }
194
195
196 /**
197 * Performs a manual product deletion.
198 *
199 * @since 2.0.0
200 *
201 * @param int[] $product_ids array of product IDs to delete
202 * @param bool $dispatch whether the job should be immediately dispatched
203 * @return \stdClass|null
204 */
205 public function start_manual_deletion( array $product_ids, $dispatch = true ) {
206
207 $job = $this->get_plugin()->get_background_job_handler()->create_job(
208 array(
209 'action' => 'delete',
210 'manual' => true,
211 'product_ids' => $product_ids,
212 )
213 );
214
215 if ( $job ) {
216
217 if ( $dispatch ) {
218 $this->get_plugin()->get_background_job_handler()->dispatch();
219 }
220 }
221
222 return $job;
223 }
224
225
226 /**
227 * Performs an interval sync with Square.
228 *
229 * @since 2.0.0
230 */
231 public function start_interval_sync() {
232
233 // bail if there is already a sync in progress
234 if ( ! $this->is_sync_enabled() || $this->is_sync_in_progress() ) {
235 return;
236 }
237
238 // use this opportunity to clear old background jobs
239 $this->get_plugin()->get_background_job_handler()->clear_all_jobs();
240
241 $job = $this->get_plugin()->get_background_job_handler()->create_job(
242 array(
243 'action' => 'poll',
244 'manual' => false,
245 'catalog_last_synced_at' => $this->get_last_synced_at(),
246 'inventory_last_synced_at' => $this->get_inventory_last_synced_at(),
247 )
248 );
249
250 if ( $job ) {
251 $this->get_plugin()->get_background_job_handler()->dispatch();
252 }
253 }
254
255
256 /** Conditional methods *******************************************************************************************/
257
258
259 /**
260 * Determines whether a sync process should happen in background.
261 *
262 * @since 2.0.0
263 *
264 * @return bool
265 */
266 public function should_sync_in_background() {
267
268 /**
269 * Filters whether a sync process should happen in background.
270 *
271 * @since 2.0.0
272 *
273 * @param bool $sync_in_background defaults to whether loopback connections are supported
274 */
275 return (bool) apply_filters( 'wc_square_sync_in_background', $this->get_plugin()->get_background_job_handler()->test_connection() );
276 }
277
278
279 /**
280 * Determines whether a sync, scheduled or manual, is in progress.
281 *
282 * @since 2.0.0
283 *
284 * @return bool
285 */
286 public function is_sync_in_progress() {
287
288 return ( defined( 'DOING_SQUARE_SYNC' ) && true === DOING_SQUARE_SYNC )
289 || null !== $this->get_job_in_progress();
290 }
291
292
293 /**
294 * Determines if sync is enabled.
295 *
296 * @since 2.0.0
297 *
298 * @return bool
299 */
300 public function is_sync_enabled() {
301
302 return $this->get_plugin()->get_settings_handler()->is_product_sync_enabled();
303 }
304
305
306 /** Setter methods ************************************************************************************************/
307
308
309 /**
310 * Records a successful sync.
311 *
312 * @since 2.0.0
313 *
314 * @param int[] $product_ids IDs of products synced
315 * @param null|\stdClass $job optional sync job, may be used to set the job ID to prevent duplicates
316 */
317 public function record_sync( array $product_ids, $job = null ) {
318
319 $products = count( $product_ids );
320
321 // only add a record of some products were synced
322 if ( $products ) {
323
324 Records::set_record(
325 array(
326 'id' => $job && isset( $job->id ) ? $job->id : null,
327 'message' => sprintf(
328 /* translators: Placeholder: %d number of products processed */
329 _n( 'Updated data for %d product.', 'Updated data for %d products.', $products, 'woocommerce-square' ),
330 $products
331 ),
332 )
333 );
334 }
335
336 /**
337 * Fires after a set of products are synced with square.
338 *
339 * @since 2.0.0
340 *
341 * @param int[] $product_ids IDs for products that were synced
342 */
343 do_action( 'wc_square_products_synced', $product_ids );
344 }
345
346
347 /**
348 * Updates the time when the last sync job occurred.
349 *
350 * @since 2.0.0
351 *
352 * @param int|string|null $timestamp a valid timestamp in UTC (optional, will default to now)
353 * @return bool success
354 */
355 public function set_last_synced_at( $timestamp = null ) {
356
357 if ( null === $timestamp ) {
358 $timestamp = current_time( 'timestamp', true );
359 }
360
361 return is_numeric( $timestamp ) && update_option( $this->last_synced_at_option_key, (int) $timestamp );
362 }
363
364
365 /** Getter methods ************************************************************************************************/
366
367
368 /**
369 * Gets a job that is currently in progress.
370 *
371 * @since 2.0.0
372 *
373 * @return null|\stdClass background job object or null if not found
374 */
375 public function get_job_in_progress() {
376
377 $handler = $this->get_plugin()->get_background_job_handler();
378
379 try {
380 $job = $handler->get_job();
381 } catch ( \Exception $e ) {
382 $job = null;
383 }
384
385 return $job && isset( $job->status ) && in_array( $job->status, array( 'created', 'queued', 'processing' ), true ) ? $job : null;
386 }
387
388
389 /**
390 * Gets a date or time of a sync job (helper method).
391 *
392 * @see Sync::get_last_synced_at()
393 * @see Sync::get_next_sync_at()
394 *
395 * @since 2.0.0
396 *
397 * @param null|int $timestamp a valid timestamp (raw data)
398 * @param string $format the output type, either 'timestamp' or a valid PHP date format for a date string
399 * @param string|null|\DateTimeZone $timezone the timezone output (defaults to the site timezone)
400 * @return int|string|null a timestamp or date, or null on error or invalid timestamp
401 */
402 private function get_sync_date_time( $timestamp, $format, $timezone ) {
403
404 $output = null;
405
406 if ( is_numeric( $timestamp ) ) {
407
408 try {
409
410 if ( null === $timezone ) {
411 $timezone = new \DateTimeZone( wc_timezone_string() );
412 } elseif ( is_string( $timezone ) ) {
413 $timezone = new \DateTimeZone( $timezone );
414 }
415
416 $date = new \DateTime( date( 'Y-m-d H:i:s', (int) $timestamp ), new \DateTimeZone( 'UTC' ) );
417 $offset = $timezone->getOffset( $date );
418 $timestamp = $date->getTimestamp() + $offset;
419
420 } catch ( \Exception $e ) {
421
422 $timestamp = null;
423 }
424 }
425
426 if ( is_numeric( $timestamp ) ) {
427 if ( 'timestamp' !== $format ) {
428 $output = date( $format, (int) $timestamp );
429 } else {
430 $output = (int) $timestamp;
431 }
432 }
433
434 return $output;
435 }
436
437
438 /**
439 * Gets the timestamp when the next sync job should start.
440 *
441 * @since 2.0.0
442 *
443 * @return int
444 */
445 public function get_next_sync_at() {
446
447 $timestamp = null;
448
449 if ( $scheduled = as_next_scheduled_action( $this->sync_scheduled_event_name ) ) {
450 $timestamp = $scheduled;
451 }
452
453 return (int) $timestamp > 1 ? $timestamp : null;
454 }
455
456
457 /**
458 * Gets the timestamp for when the last sync job completed.
459 *
460 * @since 2.0.0
461 *
462 * @return int
463 */
464 public function get_last_synced_at() {
465
466 $timestamp = get_option( $this->last_synced_at_option_key, null );
467
468 return (int) $timestamp > 1 ? $timestamp : null;
469 }
470
471
472 /**
473 * Sets the timestamp for when the last inventory sync job started.
474 *
475 * @since 2.0.0
476 *
477 * @param int|string|null $timestamp a valid timestamp in UTC (optional, will default to now)
478 * @return bool success
479 */
480 public function set_inventory_last_synced_at( $timestamp = null ) {
481
482 if ( null === $timestamp ) {
483 $timestamp = current_time( 'timestamp', true );
484 }
485
486 return is_numeric( $timestamp ) && update_option( $this->last_synced_at_option_key . '_inventory', $timestamp );
487 }
488
489
490 /**
491 * Gets the timestamp for when the last inventory sync job completed.
492 *
493 * @since 2.0.0
494 *
495 * @return int
496 */
497 public function get_inventory_last_synced_at() {
498
499 $timestamp = get_option( $this->last_synced_at_option_key . '_inventory', null );
500
501 return (int) $timestamp > 1 ? $timestamp : null;
502 }
503
504
505 /**
506 * Gets the plugin instance.
507 *
508 * @since 2.0.0
509 *
510 * @return Plugin
511 */
512 private function get_plugin() {
513
514 return $this->plugin;
515 }
516
517
518 }
519