PluginProbe
Packeta / 1.4
Packeta v1.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.4, at src/Packetery/Module/Order/Repository.php

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