PluginProbe
Packeta / 1.5.4
Packeta v1.5.4
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / src / Packetery / Module / Order / Repository.php

Repository.php in Packeta 1.5.4, at src/Packetery/Module/Order/Repository.php

597 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Repository.
4 *
5 * @package Packetery\Module\Order
6 */
7
8 declare( strict_types=1 );
9
10 namespace Packetery\Module\Order;
11
12 use Packetery\Core\Helper;
13 use Packetery\Core\Entity\Order;
14 use Packetery\Core\Entity\PickupPoint;
15 use Packetery\Core\Entity\Size;
16 use Packetery\Core\Entity\Address;
17 use Packetery\Module\Carrier;
18 use Packetery\Module\Carrier\PacketaPickupPointsConfig;
19 use Packetery\Module\ShippingMethod;
20 use Packetery\Module\WpdbAdapter;
21 use WC_Order;
22 use WP_Post;
23
24 /**
25 * Class Repository.
26 *
27 * @package Packetery\Module\Order
28 */
29 class Repository {
30
31 /**
32 * WpdbAdapter.
33 *
34 * @var WpdbAdapter
35 */
36 private $wpdbAdapter;
37
38 /**
39 * Order factory.
40 *
41 * @var Builder
42 */
43 private $builder;
44
45 /**
46 * Helper.
47 *
48 * @var Helper
49 */
50 private $helper;
51
52 /**
53 * Internal pickup points config.
54 *
55 * @var PacketaPickupPointsConfig
56 */
57 private $pickupPointsConfig;
58
59 /**
60 * Repository constructor.
61 *
62 * @param WpdbAdapter $wpdbAdapter WpdbAdapter.
63 * @param Builder $orderFactory Order factory.
64 * @param Helper $helper Helper.
65 * @param PacketaPickupPointsConfig $pickupPointsConfig Internal pickup points config.
66 */
67 public function __construct(
68 WpdbAdapter $wpdbAdapter,
69 Builder $orderFactory,
70 Helper $helper,
71 PacketaPickupPointsConfig $pickupPointsConfig
72 ) {
73 $this->wpdbAdapter = $wpdbAdapter;
74 $this->builder = $orderFactory;
75 $this->helper = $helper;
76 $this->pickupPointsConfig = $pickupPointsConfig;
77 }
78
79 /**
80 * Applies custom order status filter.
81 *
82 * @param array $clauses Query clauses.
83 * @param \WP_Query $queryObject WP Query.
84 * @param array $paramValues Param values.
85 *
86 * @return void
87 */
88 private function applyCustomFilters( array &$clauses, \WP_Query $queryObject, array $paramValues ): void {
89 /**
90 * Filters order statuses to exclude in Packeta order list filtering.
91 *
92 * @since 1.4
93 *
94 * @param array $orderStatusesToExclude Order statuses to exclude.
95 * @param \WP_Query $queryObject WP Query.
96 * @param array $paramValues Param values.
97 */
98 $orderStatusesToExclude = (array) apply_filters( 'packetery_exclude_orders_with_status', [], $queryObject, $paramValues );
99 if ( ! $orderStatusesToExclude ) {
100 return;
101 }
102
103 $clauses['where'] .= sprintf( ' AND `%s`.`post_status` NOT IN (%s)', $this->wpdbAdapter->posts, $this->wpdbAdapter->prepareInClause( $orderStatusesToExclude ) );
104 }
105
106 /**
107 * Extends WP_Query to include custom table.
108 *
109 * @link https://wordpress.stackexchange.com/questions/50305/how-to-extend-wp-query-to-include-custom-table-in-query
110 *
111 * @param array $clauses Clauses.
112 * @param \WP_Query $queryObject WP_Query.
113 * @param array $paramValues Param values.
114 *
115 * @return array
116 */
117 public function processPostClauses( array $clauses, \WP_Query $queryObject, array $paramValues ): array {
118 if ( isset( $queryObject->query['post_type'] ) &&
119 (
120 'shop_order' === $queryObject->query['post_type'] ||
121 ( is_array( $queryObject->query['post_type'] ) && in_array( 'shop_order', $queryObject->query['post_type'], true ) )
122 )
123 ) {
124 // TODO: Introduce variable.
125 $clauses['join'] .= ' LEFT JOIN `' . $this->wpdbAdapter->packetery_order . '` ON `' . $this->wpdbAdapter->packetery_order . '`.`id` = `' . $this->wpdbAdapter->posts . '`.`id`';
126
127 if ( $paramValues['packetery_carrier_id'] ) {
128 $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packetery_order . '`.`carrier_id` = "' . esc_sql( $paramValues['packetery_carrier_id'] ) . '"';
129 $this->applyCustomFilters( $clauses, $queryObject, $paramValues );
130 }
131 if ( $paramValues['packetery_to_submit'] ) {
132 $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packetery_order . '`.`carrier_id` IS NOT NULL ';
133 $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packetery_order . '`.`is_exported` = false ';
134 $this->applyCustomFilters( $clauses, $queryObject, $paramValues );
135 }
136 if ( $paramValues['packetery_to_print'] ) {
137 $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packetery_order . '`.`packet_id` IS NOT NULL ';
138 $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packetery_order . '`.`is_label_printed` = false ';
139 $this->applyCustomFilters( $clauses, $queryObject, $paramValues );
140 }
141 if ( $paramValues['packetery_order_type'] ) {
142 if ( Carrier\Repository::INTERNAL_PICKUP_POINTS_ID === $paramValues['packetery_order_type'] ) {
143 $comparison = 'IN';
144 } else {
145 $comparison = 'NOT IN';
146 }
147 $internalCarriers = array_keys( $this->pickupPointsConfig->getVendorCarriers() );
148 $internalCarriers[] = Carrier\Repository::INTERNAL_PICKUP_POINTS_ID;
149 $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packetery_order . '`.`carrier_id` ' . $comparison . ' (' . $this->wpdbAdapter->prepareInClause( $internalCarriers ) . ')';
150 $this->applyCustomFilters( $clauses, $queryObject, $paramValues );
151 }
152 }
153
154 return $clauses;
155 }
156
157 /**
158 * Create table to store orders.
159 *
160 * @return bool
161 */
162 public function createOrAlterTable(): bool {
163 $createTableQuery = 'CREATE TABLE ' . $this->wpdbAdapter->packetery_order . ' (
164 `id` bigint(20) unsigned NOT NULL,
165 `carrier_id` varchar(255) NOT NULL,
166 `is_exported` tinyint(1) NOT NULL,
167 `packet_id` varchar(255) NULL,
168 `is_label_printed` tinyint(1) NOT NULL,
169 `point_id` varchar(255) NULL,
170 `point_name` varchar(255) NULL,
171 `point_url` varchar(255) NULL,
172 `point_street` varchar(255) NULL,
173 `point_zip` varchar(255) NULL,
174 `point_city` varchar(255) NULL,
175 `address_validated` tinyint(1) NOT NULL,
176 `delivery_address` text NULL,
177 `weight` float NULL,
178 `length` float NULL,
179 `width` float NULL,
180 `height` float NULL,
181 `adult_content` tinyint(1) NULL,
182 `value` double NULL,
183 `cod` double NULL,
184 `api_error_message` text NULL,
185 `api_error_date` datetime NULL,
186 `carrier_number` varchar(255) NULL,
187 `packet_status` varchar(255) NULL,
188 `deliver_on` date NULL,
189 PRIMARY KEY (`id`)
190 ) ' . $this->wpdbAdapter->get_charset_collate();
191
192 return $this->wpdbAdapter->dbDelta( $createTableQuery, $this->wpdbAdapter->packetery_order );
193 }
194
195 /**
196 * Drop table used to store orders.
197 */
198 public function drop(): void {
199 $this->wpdbAdapter->query( 'DROP TABLE IF EXISTS `' . $this->wpdbAdapter->packetery_order . '`' );
200 }
201
202 /**
203 * Gets order data.
204 *
205 * @param int $id Order id.
206 *
207 * @return Order|null
208 */
209 public function getById( int $id ): ?Order {
210 $wcOrder = $this->getWcOrderById( $id );
211 if ( null === $wcOrder ) {
212 return null;
213 }
214
215 return $this->getByWcOrder( $wcOrder );
216 }
217
218 /**
219 * Gets order by wc order.
220 *
221 * @param WC_Order $wcOrder WC Order.
222 *
223 * @return Order|null
224 */
225 public function getByWcOrder( WC_Order $wcOrder ): ?Order {
226 if ( ! $wcOrder->has_shipping_method( ShippingMethod::PACKETERY_METHOD_ID ) ) {
227 return null;
228 }
229
230 $result = $this->wpdbAdapter->get_row(
231 $this->wpdbAdapter->prepare(
232 '
233 SELECT o.* FROM `' . $this->wpdbAdapter->packetery_order . '` o
234 JOIN `' . $this->wpdbAdapter->posts . '` as wp_p ON wp_p.ID = o.id
235 WHERE o.`id` = %d',
236 $wcOrder->get_id()
237 )
238 );
239
240 if ( ! $result ) {
241 return null;
242 }
243
244 $partialOrder = $this->createPartialOrder( $result );
245 return $this->builder->finalize( $wcOrder, $partialOrder );
246 }
247
248 /**
249 * Creates partial order.
250 *
251 * @param \stdClass $result DB result.
252 *
253 * @return Order
254 */
255 private function createPartialOrder( \stdClass $result ): Order {
256 $partialOrder = new Order(
257 $result->id,
258 $result->carrier_id
259 );
260
261 $orderWeight = $this->parseFloat( $result->weight );
262 if ( null !== $orderWeight ) {
263 $partialOrder->setWeight( $orderWeight );
264 }
265 $partialOrder->setPacketId( $result->packet_id );
266 $partialOrder->setSize( new Size( $this->parseFloat( $result->length ), $this->parseFloat( $result->width ), $this->parseFloat( $result->height ) ) );
267 $partialOrder->setIsExported( (bool) $result->is_exported );
268 $partialOrder->setIsLabelPrinted( (bool) $result->is_label_printed );
269 $partialOrder->setCarrierNumber( $result->carrier_number );
270 $partialOrder->setPacketStatus( $result->packet_status );
271 $partialOrder->setAddressValidated( (bool) $result->address_validated );
272 $partialOrder->setAdultContent( $this->parseBool( $result->adult_content ) );
273 $partialOrder->setValue( $this->parseFloat( $result->value ) );
274 $partialOrder->setCod( $this->parseFloat( $result->cod ) );
275 $partialOrder->setDeliverOn( $this->helper->getDateTimeFromString( $result->deliver_on ) );
276 $partialOrder->setLastApiErrorMessage( $result->api_error_message );
277 $partialOrder->setLastApiErrorDateTime(
278 ( null === $result->api_error_date )
279 ? null
280 : \DateTimeImmutable::createFromFormat(
281 Helper::MYSQL_DATETIME_FORMAT,
282 $result->api_error_date,
283 new \DateTimeZone( 'UTC' )
284 )->setTimezone( wp_timezone() )
285 );
286
287 if ( $result->delivery_address ) {
288 $deliveryAddressDecoded = json_decode( $result->delivery_address, false );
289 $deliveryAddress = new Address(
290 $deliveryAddressDecoded->street,
291 $deliveryAddressDecoded->city,
292 $deliveryAddressDecoded->zip
293 );
294
295 $deliveryAddress->setHouseNumber( $deliveryAddressDecoded->houseNumber );
296 $deliveryAddress->setLongitude( $deliveryAddressDecoded->longitude );
297 $deliveryAddress->setLatitude( $deliveryAddressDecoded->latitude );
298 $deliveryAddress->setCounty( $deliveryAddressDecoded->county );
299
300 $partialOrder->setDeliveryAddress( $deliveryAddress );
301 }
302
303 if ( null !== $result->point_id ) {
304 $pickUpPoint = new PickupPoint(
305 $result->point_id,
306 $result->point_name,
307 $result->point_city,
308 $result->point_zip,
309 $result->point_street,
310 $result->point_url
311 );
312
313 $partialOrder->setPickupPoint( $pickUpPoint );
314 }
315
316 return $partialOrder;
317 }
318
319 /**
320 * Parses string value as float.
321 *
322 * @param string|float|null $value Value.
323 *
324 * @return float|null
325 */
326 private function parseFloat( $value ): ?float {
327 if ( null === $value || '' === $value ) {
328 return null;
329 }
330
331 return (float) $value;
332 }
333
334 /**
335 * Parses string value as float.
336 *
337 * @param string|int|null $value Value.
338 *
339 * @return bool|null
340 */
341 private function parseBool( $value ): ?bool {
342 if ( null === $value || '' === $value ) {
343 return null;
344 }
345
346 return (bool) $value;
347 }
348
349 /**
350 * Transforms order to DB array.
351 *
352 * @param Order $order Order.
353 *
354 * @return array
355 */
356 private function orderToDbArray( Order $order ): array {
357 $point = $order->getPickupPoint();
358 if ( null === $point ) {
359 $point = new PickupPoint();
360 }
361
362 $deliveryAddress = null;
363 if ( $order->isAddressValidated() && $order->getDeliveryAddress() ) {
364 $deliveryAddress = wp_json_encode( $order->getDeliveryAddress()->export() );
365 }
366
367 $apiErrorDateTime = $order->getLastApiErrorDateTime();
368 if ( null !== $apiErrorDateTime ) {
369 $apiErrorDateTime = $apiErrorDateTime->format( Helper::MYSQL_DATETIME_FORMAT );
370 }
371
372 $data = [
373 'id' => (int) $order->getNumber(),
374 'carrier_id' => $order->getCarrierId(),
375 'is_exported' => (int) $order->isExported(),
376 'packet_id' => $order->getPacketId(),
377 'packet_status' => $order->getPacketStatus(),
378 'is_label_printed' => (int) $order->isLabelPrinted(),
379 'carrier_number' => $order->getCarrierNumber(),
380 'weight' => $order->getWeight(),
381 'point_id' => $point->getId(),
382 'point_name' => $point->getName(),
383 'point_url' => $point->getUrl(),
384 'point_street' => $point->getStreet(),
385 'point_zip' => $point->getZip(),
386 'point_city' => $point->getCity(),
387 'address_validated' => (int) $order->isAddressValidated(),
388 'delivery_address' => $deliveryAddress,
389 'length' => $order->getLength(),
390 'width' => $order->getWidth(),
391 'height' => $order->getHeight(),
392 'adult_content' => $order->containsAdultContent(),
393 'cod' => $order->getCod(),
394 'value' => $order->getValue(),
395 'api_error_message' => $order->getLastApiErrorMessage(),
396 'api_error_date' => $apiErrorDateTime,
397 'deliver_on' => $this->helper->getStringFromDateTime( $order->getDeliverOn(), $this->helper::DATEPICKER_FORMAT ),
398 ];
399
400 return $data;
401 }
402
403 /**
404 * Saves order.
405 *
406 * @param Order $order Order.
407 *
408 * @return void
409 */
410 public function save( Order $order ): void {
411 $this->wpdbAdapter->insertReplaceHelper( $this->wpdbAdapter->packetery_order, $this->orderToDbArray( $order ), null, 'REPLACE' );
412 }
413
414 /**
415 * Loads order entities by list of ids.
416 *
417 * @param array $orderIds Order ids.
418 *
419 * @return Order[]
420 */
421 public function getByIds( array $orderIds ): array {
422 if ( empty( $orderIds ) ) {
423 return [];
424 }
425
426 $wpdbAdapter = $this->wpdbAdapter;
427 $ordersIdsPlaceholder = implode( ', ', array_fill( 0, count( $orderIds ), '%d' ) );
428 $packeteryOrdersResult = $wpdbAdapter->get_results(
429 $wpdbAdapter->prepare(
430 'SELECT * FROM `' . $wpdbAdapter->packetery_order . '`
431 WHERE `id` IN (' . $ordersIdsPlaceholder . ')',
432 $orderIds
433 ),
434 OBJECT_K
435 );
436
437 $orderEntities = [];
438 foreach ( $orderIds as $orderId ) {
439 if ( ! isset( $packeteryOrdersResult[ $orderId ] ) ) {
440 continue;
441 }
442
443 $wcOrder = $this->getWcOrderById( (int) $orderId );
444 assert( null !== $wcOrder, 'WC order has to be present' );
445
446 $partialOrder = $this->createPartialOrder( $packeteryOrdersResult[ $orderId ] );
447 $orderEntities[ $orderId ] = $this->builder->finalize( $wcOrder, $partialOrder );
448 }
449
450 return $orderEntities;
451 }
452
453 /**
454 * Finds orders.
455 *
456 * @param array $allowedPacketStatuses Allowed packet statuses.
457 * @param array $allowedOrderStatuses Allowed order statuses.
458 * @param int $maxDays Max number of days of single packet sync.
459 * @param int $limit Number of records.
460 *
461 * @return iterable|Order[]
462 */
463 public function findStatusSyncingOrders( array $allowedPacketStatuses, array $allowedOrderStatuses, int $maxDays, int $limit ): iterable {
464 $dateLimit = Helper::now()->modify( '- ' . $maxDays . ' days' )->format( 'Y-m-d H:i:s' );
465
466 $andWhere = [];
467
468 $andWhere[] = 'o.`packet_id` IS NOT NULL';
469 $andWhere[] = $this->wpdbAdapter->prepare( 'wp_p.`post_date_gmt` >= %s', $dateLimit );
470
471 $orPacketStatus = [];
472 $orPacketStatus[] = 'o.`packet_status` IS NULL';
473
474 if ( $allowedPacketStatuses ) {
475 $orPacketStatus[] = '`o`.`packet_status` IN (' . $this->wpdbAdapter->prepareInClause( $allowedOrderStatuses ) . ')';
476 }
477
478 if ( $orPacketStatus ) {
479 $andWhere[] = '(' . implode( ' OR ', $orPacketStatus ) . ')';
480 }
481
482 if ( $allowedOrderStatuses ) {
483 $andWhere[] = '`wp_p`.`post_status` IN (' . $this->wpdbAdapter->prepareInClause( $allowedOrderStatuses ) . ')';
484 } else {
485 $andWhere[] = '1 = 0';
486 }
487
488 $where = '';
489 if ( $andWhere ) {
490 $where = ' WHERE ' . implode( ' AND ', $andWhere );
491 }
492
493 $sql = $this->wpdbAdapter->prepare(
494 '
495 SELECT o.* FROM `' . $this->wpdbAdapter->packetery_order . '` o
496 JOIN `' . $this->wpdbAdapter->posts . '` wp_p ON wp_p.`ID` = o.`id`
497 ' . $where . '
498 ORDER BY wp_p.`post_date`
499 LIMIT %d',
500 $limit
501 );
502
503 $rows = $this->wpdbAdapter->get_results( $sql );
504
505 foreach ( $rows as $row ) {
506 $wcOrder = $this->getWcOrderById( (int) $row->id );
507 if ( null === $wcOrder || ! $wcOrder->has_shipping_method( ShippingMethod::PACKETERY_METHOD_ID ) ) {
508 continue;
509 }
510
511 $partial = $this->createPartialOrder( $row );
512 yield $this->builder->finalize( $wcOrder, $partial );
513 }
514 }
515
516 /**
517 * Counts order to be submitted.
518 *
519 * @return int
520 */
521 public function countOrdersToSubmit(): int {
522 return (int) $this->wpdbAdapter->get_var(
523 'SELECT COUNT(DISTINCT o.id) FROM `' . $this->wpdbAdapter->packetery_order . '` o
524 JOIN `' . $this->wpdbAdapter->posts . '` wp_p ON wp_p.`ID` = o.`id`
525 WHERE o.`carrier_id` IS NOT NULL AND o.`is_exported` = false'
526 );
527 }
528
529 /**
530 * Counts order to be printed.
531 *
532 * @return int
533 */
534 public function countOrdersToPrint(): int {
535 return (int) $this->wpdbAdapter->get_var(
536 'SELECT COUNT(DISTINCT o.id) FROM `' . $this->wpdbAdapter->packetery_order . '` o
537 JOIN `' . $this->wpdbAdapter->posts . '` wp_p ON wp_p.`ID` = o.`id`
538 WHERE o.`packet_id` IS NOT NULL AND o.`is_label_printed` = false'
539 );
540 }
541
542 /**
543 * Deletes all custom table records linked to permanently deleted orders.
544 *
545 * @return void
546 */
547 public function deleteOrphans(): void {
548 $this->wpdbAdapter->query(
549 'DELETE `' . $this->wpdbAdapter->packetery_order . '` FROM `' . $this->wpdbAdapter->packetery_order . '`
550 LEFT JOIN `' . $this->wpdbAdapter->posts . '` ON `' . $this->wpdbAdapter->posts . '`.`ID` = `' . $this->wpdbAdapter->packetery_order . '`.`id`
551 WHERE `' . $this->wpdbAdapter->posts . '`.`ID` IS NULL'
552 );
553 }
554
555 /**
556 * Deletes data from custom table.
557 *
558 * @param int $orderId Order id.
559 *
560 * @return void
561 */
562 private function delete( int $orderId ): void {
563 $this->wpdbAdapter->delete( $this->wpdbAdapter->packetery_order, [ 'id' => $orderId ], '%d' );
564 }
565
566 /**
567 * Fires after post deletion.
568 *
569 * @param int $postId Post id.
570 * @param WP_Post $post Post object.
571 *
572 * @return void
573 */
574 public function deletedPostHook( int $postId, WP_Post $post ): void {
575 if ( 'shop_order' === $post->post_type ) {
576 $this->delete( $postId );
577 }
578 }
579
580 /**
581 * Gets WC_Order object.
582 *
583 * @param int $id Order id.
584 *
585 * @return WC_Order|null
586 */
587 public function getWcOrderById( int $id ): ?WC_Order {
588 $result = wc_get_order( $id );
589 if ( $result instanceof WC_Order ) {
590 return $result;
591 }
592
593 return null;
594 }
595
596 }
597