PluginProbe
Packeta / 2.3.2
Packeta v2.3.2
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
← All changes | src/Packetery/Module/Order/Repository.php +562 -243 1.2.42.3.2 View file →
@@ -8,16 +8,26 @@
8 8 declare( strict_types=1 );
9 9
10 10 namespace Packetery\Module\Order;
11 11
12 +use DateTimeImmutable;
13 +use Exception;
14 +use Packetery\Core\CoreHelper;
15 +use Packetery\Core\Entity;
12 16 use Packetery\Core\Entity\Order;
13 17 use Packetery\Core\Entity\PickupPoint;
14 -use Packetery\Core\Entity\Size;
15 -use Packetery\Core\Entity\Address;
16 -use Packetery\Module\Calculator;
17 18 use Packetery\Module\Carrier;
18 -use Packetery\Module\Product;
19 -use Packetery\Module\ShippingMethod;
19 +use Packetery\Module\Carrier\PacketaPickupPointsConfig;
20 +use Packetery\Module\CustomsDeclaration;
21 +use Packetery\Module\Exception\DeleteErrorException;
22 +use Packetery\Module\Exception\InvalidCarrierException;
23 +use Packetery\Module\Framework\WcAdapter;
24 +use Packetery\Module\ModuleHelper;
25 +use Packetery\Module\Shipping\ShippingProvider;
26 +use Packetery\Module\WcLogger;
27 +use Packetery\Module\WpdbAdapter;
28 +use WC_Order;
29 +use WP_Post;
20 30
21 31 /**
22 32 * Class Repository.
23 33 *
@@ -25,13 +35,13 @@
25 35 */
26 36 class Repository {
27 37
28 38 /**
29 - * Wpdb.
39 + * WpdbAdapter.
30 40 *
31 - * @var \wpdb
41 + * @var WpdbAdapter
32 42 */
33 - private $wpdb;
43 + private $wpdbAdapter;
34 44
35 45 /**
36 46 * Order factory.
37 47 *
@@ -39,66 +49,147 @@
39 49 */
40 50 private $builder;
41 51
42 52 /**
43 - * Calculator.
53 + * CoreHelper.
44 54 *
45 - * @var Calculator
55 + * @var CoreHelper
46 56 */
47 - private $calculator;
57 + private $coreHelper;
48 58
49 59 /**
60 + * Internal pickup points config.
61 + *
62 + * @var PacketaPickupPointsConfig
63 + */
64 + private $pickupPointsConfig;
65 +
66 + /**
67 + * Carrier repository.
68 + *
69 + * @var Carrier\EntityRepository
70 + */
71 + private $carrierRepository;
72 +
73 + /**
74 + * Customs declaration repository.
75 + *
76 + * @var CustomsDeclaration\Repository
77 + */
78 + private $customsDeclarationRepository;
79 +
80 + /**
81 + * @var WcAdapter
82 + */
83 + private $wcAdapter;
84 +
85 + /**
50 86 * Repository constructor.
51 87 *
52 - * @param \wpdb $wpdb Wpdb.
53 - * @param Builder $orderFactory Order factory.
54 - * @param Calculator $calculator Calculator.
88 + * @param WpdbAdapter $wpdbAdapter WpdbAdapter.
89 + * @param Builder $orderFactory Order factory.
90 + * @param CoreHelper $coreHelper CoreHelper.
91 + * @param PacketaPickupPointsConfig $pickupPointsConfig Internal pickup points config.
92 + * @param Carrier\EntityRepository $carrierRepository Carrier repository.
93 + * @param CustomsDeclaration\Repository $customsDeclarationRepository Customs declaration repository.
55 94 */
56 - public function __construct( \wpdb $wpdb, Builder $orderFactory, Calculator $calculator ) {
57 - $this->wpdb = $wpdb;
58 - $this->builder = $orderFactory;
59 - $this->calculator = $calculator;
95 + public function __construct(
96 + WpdbAdapter $wpdbAdapter,
97 + Builder $orderFactory,
98 + CoreHelper $coreHelper,
99 + PacketaPickupPointsConfig $pickupPointsConfig,
100 + Carrier\EntityRepository $carrierRepository,
101 + CustomsDeclaration\Repository $customsDeclarationRepository,
102 + WcAdapter $wcAdapter
103 + ) {
104 + $this->wpdbAdapter = $wpdbAdapter;
105 + $this->builder = $orderFactory;
106 + $this->coreHelper = $coreHelper;
107 + $this->pickupPointsConfig = $pickupPointsConfig;
108 + $this->carrierRepository = $carrierRepository;
109 + $this->customsDeclarationRepository = $customsDeclarationRepository;
110 + $this->wcAdapter = $wcAdapter;
60 111 }
61 112
62 113 /**
63 - * Extends WP_Query to include custom table.
114 + * Applies custom order status filter.
64 115 *
65 - * @link https://wordpress.stackexchange.com/questions/50305/how-to-extend-wp-query-to-include-custom-table-in-query
116 + * @param array<string, string|null> $clauses Query clauses.
117 + * @param \WP_Query|null $queryObject WP Query.
118 + * @param array<string, string|null> $paramValues Param values.
66 119 *
67 - * @param array $clauses Clauses.
68 - * @param \WP_Query $queryObject WP_Query.
69 - * @param array $paramValues Param values.
120 + * @return void
121 + */
122 + private function applyCustomFilters( array &$clauses, ?\WP_Query $queryObject, array $paramValues ): void {
123 + /**
124 + * Filters order statuses to exclude in Packeta order list filtering.
125 + *
126 + * @since 1.4
127 + *
128 + * @param array $orderStatusesToExclude Order statuses to exclude.
129 + * @param \WP_Query|null $queryObject WP Query.
130 + * @param array $paramValues Param values.
131 + */
132 + $orderStatusesToExclude = (array) apply_filters( 'packetery_exclude_orders_with_status', [], $queryObject, $paramValues );
133 + if ( count( $orderStatusesToExclude ) === 0 ) {
134 + return;
135 + }
136 +
137 + if ( ModuleHelper::isHposEnabled() ) {
138 + $clauses['where'] .= sprintf( ' AND `%s`.`status` NOT IN (%s)', $this->wpdbAdapter->wcOrders, $this->wpdbAdapter->prepareInClause( $orderStatusesToExclude ) );
139 + } else {
140 + $clauses['where'] .= sprintf( ' AND `%s`.`post_status` NOT IN (%s)', $this->wpdbAdapter->posts, $this->wpdbAdapter->prepareInClause( $orderStatusesToExclude ) );
141 + }
142 + }
143 +
144 + /**
145 + * Extends SQL query clauses.
70 146 *
71 - * @return array
147 + * @param array<string, string> $clauses Clauses.
148 + * @param \WP_Query|null $queryObject Query object.
149 + * @param array<string, string|null> $paramValues Param values.
150 + *
151 + * @return array<string, string>
72 152 */
73 - public function processPostClauses( array $clauses, \WP_Query $queryObject, array $paramValues ): array {
74 - if ( isset( $queryObject->query['post_type'] ) &&
75 - (
76 - 'shop_order' === $queryObject->query['post_type'] ||
77 - ( is_array( $queryObject->query['post_type'] ) && in_array( 'shop_order', $queryObject->query['post_type'], true ) )
78 - )
79 - ) {
80 - // TODO: Introduce variable.
81 - $clauses['join'] .= ' LEFT JOIN `' . $this->wpdb->packetery_order . '` ON `' . $this->wpdb->packetery_order . '`.`id` = `' . $this->wpdb->posts . '`.`id`';
153 + public function processClauses( array $clauses, ?\WP_Query $queryObject, array $paramValues ): array {
154 + if ( ModuleHelper::isHposEnabled() ) {
155 + $clauses['join'] .= ' LEFT JOIN `' . $this->wpdbAdapter->packeteryOrder . '` ON `' . $this->wpdbAdapter->packeteryOrder . '`.`id` = `' . $this->wpdbAdapter->wcOrders . '`.`id`';
156 + } else {
157 + $clauses['join'] .= ' LEFT JOIN `' . $this->wpdbAdapter->packeteryOrder . '` ON `' . $this->wpdbAdapter->packeteryOrder . '`.`id` = `' . $this->wpdbAdapter->posts . '`.`id`';
158 + }
82 159
83 - if ( $paramValues['packetery_carrier_id'] ) {
84 - $clauses['where'] .= ' AND `' . $this->wpdb->packetery_order . '`.`carrier_id` = "' . $this->wpdb->_real_escape( $paramValues['packetery_carrier_id'] ) . '"';
160 + if ( isset( $paramValues['packetery_carrier_id'] ) && $paramValues['packetery_carrier_id'] !== '' ) {
161 + $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packeteryOrder . '`.`carrier_id` = "' . esc_sql( $paramValues['packetery_carrier_id'] ) . '"';
162 + $this->applyCustomFilters( $clauses, $queryObject, $paramValues );
163 + }
164 + if ( isset( $paramValues['packetery_to_submit'] ) && $paramValues['packetery_to_submit'] !== '' ) {
165 + $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packeteryOrder . '`.`carrier_id` IS NOT NULL ';
166 + $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packeteryOrder . '`.`is_exported` = false ';
167 + $this->applyCustomFilters( $clauses, $queryObject, $paramValues );
168 + }
169 + if ( isset( $paramValues['packetery_to_print'] ) && $paramValues['packetery_to_print'] !== '' ) {
170 + $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packeteryOrder . '`.`packet_id` IS NOT NULL ';
171 + $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packeteryOrder . '`.`is_label_printed` = false ';
172 + $this->applyCustomFilters( $clauses, $queryObject, $paramValues );
173 + }
174 + if ( isset( $paramValues['packetery_order_type'] ) && $paramValues['packetery_order_type'] !== '' ) {
175 + if ( $paramValues['packetery_order_type'] === Entity\Carrier::INTERNAL_PICKUP_POINTS_ID ) {
176 + $comparison = 'IN';
177 + } else {
178 + $comparison = 'NOT IN';
85 179 }
86 - if ( $paramValues['packetery_to_submit'] ) {
87 - $clauses['where'] .= ' AND `' . $this->wpdb->packetery_order . '`.`carrier_id` IS NOT NULL ';
88 - $clauses['where'] .= ' AND `' . $this->wpdb->packetery_order . '`.`is_exported` = false ';
180 + $internalCarriers = array_keys( $this->carrierRepository->getNonFeedCarriers() );
181 + $internalCarriers[] = Entity\Carrier::INTERNAL_PICKUP_POINTS_ID;
182 + $clauses['where'] .= ' AND `' . $this->wpdbAdapter->packeteryOrder . '`.`carrier_id` ' . $comparison . ' (' . $this->wpdbAdapter->prepareInClause( $internalCarriers ) . ')';
183 + $this->applyCustomFilters( $clauses, $queryObject, $paramValues );
184 + }
185 + if ( isset( $paramValues['orderby'] ) && $paramValues['orderby'] === 'packetery_packet_stored_until' ) {
186 + if ( $paramValues['order'] === 'asc' ) {
187 + $clauses['orderby'] = '`' . $this->wpdbAdapter->packeteryOrder . '`.`stored_until` ASC';
89 188 }
90 - if ( $paramValues['packetery_to_print'] ) {
91 - $clauses['where'] .= ' AND `' . $this->wpdb->packetery_order . '`.`packet_id` IS NOT NULL ';
92 - $clauses['where'] .= ' AND `' . $this->wpdb->packetery_order . '`.`is_label_printed` = false ';
189 + if ( $paramValues['order'] === 'desc' ) {
190 + $clauses['orderby'] = '`' . $this->wpdbAdapter->packeteryOrder . '`.`stored_until` DESC';
93 191 }
94 - if ( $paramValues['packetery_order_type'] ) {
95 - if ( Carrier\Repository::INTERNAL_PICKUP_POINTS_ID === $paramValues['packetery_order_type'] ) {
96 - $clauses['where'] .= ' AND `' . $this->wpdb->packetery_order . '`.`carrier_id` = "' . $this->wpdb->_real_escape( Carrier\Repository::INTERNAL_PICKUP_POINTS_ID ) . '"';
97 - } else {
98 - $clauses['where'] .= ' AND `' . $this->wpdb->packetery_order . '`.`carrier_id` != "' . $this->wpdb->_real_escape( Carrier\Repository::INTERNAL_PICKUP_POINTS_ID ) . '"';
99 - }
100 - }
101 192 }
102 193
103 194 return $clauses;
104 195 }
@@ -107,167 +198,154 @@
107 198 * Create table to store orders.
108 199 *
109 200 * @return bool
110 201 */
111 - public function createTable(): bool {
112 - $wpdb = $this->wpdb;
202 + public function createOrAlterTable(): bool {
203 + $createTableQuery = 'CREATE TABLE ' . $this->wpdbAdapter->packeteryOrder . ' (
204 + `id` bigint(20) unsigned NOT NULL,
205 + `carrier_id` varchar(15) NOT NULL,
206 + `is_exported` tinyint(1) NOT NULL,
207 + `packet_id` varchar(15) NULL,
208 + `packet_claim_id` varchar(15) NULL,
209 + `packet_claim_password` varchar(10) NULL,
210 + `consign_password` varchar(10) NULL,
211 + `is_label_printed` tinyint(1) NOT NULL,
212 + `point_id` varchar(50) NULL,
213 + `point_place` varchar(100) NULL,
214 + `point_name` varchar(150) NULL,
215 + `point_url` varchar(255) NULL,
216 + `point_street` varchar(120) NULL,
217 + `point_zip` varchar(10) NULL,
218 + `point_city` varchar(70) NULL,
219 + `address_validated` tinyint(1) NOT NULL,
220 + `delivery_address` text NULL,
221 + `weight` float NULL,
222 + `length` float NULL,
223 + `width` float NULL,
224 + `height` float NULL,
225 + `adult_content` tinyint(1) NULL,
226 + `value` double NULL,
227 + `cod` double NULL,
228 + `api_error_message` text NULL,
229 + `api_error_date` datetime NULL,
230 + `carrier_number` varchar(50) NULL,
231 + `packet_status` varchar(30) NULL,
232 + `stored_until` date NULL,
233 + `deliver_on` date NULL,
234 + `car_delivery_id` varchar(15) NULL,
235 + PRIMARY KEY (`id`)
236 + ) ' . $this->wpdbAdapter->get_charset_collate();
113 237
114 - return $wpdb->query(
115 - 'CREATE TABLE IF NOT EXISTS `' . $wpdb->packetery_order . '` (
116 - `id` bigint(20) unsigned NOT NULL,
117 - `carrier_id` varchar(255) NOT NULL,
118 - `is_exported` boolean NOT NULL,
119 - `packet_id` varchar(255) NULL,
120 - `is_label_printed` boolean NOT NULL,
121 - `point_id` varchar(255) NULL,
122 - `point_name` varchar(255) NULL,
123 - `point_url` varchar(255) NULL,
124 - `point_street` varchar(255) NULL,
125 - `point_zip` varchar(255) NULL,
126 - `point_city` varchar(255) NULL,
127 - `address_validated` boolean NOT NULL,
128 - `delivery_address` TEXT NULL,
129 - `weight` float NULL,
130 - `length` float NULL,
131 - `width` float NULL,
132 - `height` float NULL,
133 - `carrier_number` varchar(255) NULL,
134 - `packet_status` varchar(255) NULL,
135 - PRIMARY KEY (`id`)
136 - ) ' . $wpdb->get_charset_collate()
137 - );
238 + return $this->wpdbAdapter->dbDelta( $createTableQuery, $this->wpdbAdapter->packeteryOrder );
138 239 }
139 240
140 241 /**
141 - * Drop table used to store orders.
242 + * @throws InvalidCarrierException InvalidCarrierException.
142 243 */
143 - public function drop(): void {
144 - $wpdb = $this->wpdb;
145 - $wpdb->query( 'DROP TABLE IF EXISTS `' . $wpdb->packetery_order . '`' );
244 + public function getById( int $id ): ?Order {
245 + $wcOrder = $this->getWcOrderById( $id );
246 + if ( $wcOrder === null ) {
247 + return null;
248 + }
249 +
250 + return $this->getByWcOrder( $wcOrder );
146 251 }
147 252
148 253 /**
149 - * Gets order data.
150 - *
151 - * @param int $id Order id.
152 - *
153 - * @return Order|null
254 + * Ignores InvalidCarrierException.
154 255 */
155 - public function getById( int $id ): ?Order {
156 - $wcOrder = wc_get_order( $id );
157 - if ( ! $wcOrder instanceof \WC_Order ) {
256 + public function getByIdWithValidCarrier( int $id ): ?Order {
257 + $wcOrder = $this->getWcOrderById( $id );
258 + if ( $wcOrder === null ) {
158 259 return null;
159 260 }
160 261
161 - return $this->getByWcOrder( $wcOrder );
262 + return $this->getByWcOrderWithValidCarrier( $wcOrder );
162 263 }
163 264
164 265 /**
165 - * Gets order by wc order.
266 + * Returns Packeta order, or null.
166 267 *
167 - * @param \WC_Order $wcOrder WC Order.
268 + * @param int $id Order ID.
168 269 *
169 270 * @return Order|null
170 271 */
171 - public function getByWcOrder( \WC_Order $wcOrder ): ?Order {
172 - if ( ! $wcOrder->has_shipping_method( ShippingMethod::PACKETERY_METHOD_ID ) ) {
272 + public function findById( int $id ): ?Order {
273 + $wcOrder = $this->getWcOrderById( $id );
274 + if ( $wcOrder === null ) {
173 275 return null;
174 276 }
175 277
176 - $wpdb = $this->wpdb;
278 + $result = $this->getDataById( $wcOrder->get_id() );
279 + if ( $result === null ) {
280 + return null;
281 + }
177 282
178 - $result = $wpdb->get_row(
179 - $wpdb->prepare(
180 - '
181 - SELECT o.* FROM `' . $wpdb->packetery_order . '` o
182 - JOIN `' . $wpdb->posts . '` as wp_p ON wp_p.ID = o.id
183 - WHERE o.`id` = %d',
184 - $wcOrder->get_id()
185 - )
186 - );
187 -
188 - if ( ! $result ) {
283 + try {
284 + return $this->builder->build( $wcOrder, $result );
285 + } catch ( InvalidCarrierException $invalidCarrierException ) {
189 286 return null;
190 287 }
191 -
192 - $partialOrder = $this->createPartialOrder( $wcOrder, $result );
193 - return $this->builder->finalize( $wcOrder, $partialOrder );
194 288 }
195 289
196 290 /**
197 - * Creates partial order.
291 + * Gets Packeta order data by id.
198 292 *
199 - * @param \WC_Order $wcOrder WC Order.
200 - * @param \stdClass $result DB result.
293 + * @param int $id Order id.
201 294 *
202 - * @return Order
295 + * @return object|null
203 296 */
204 - private function createPartialOrder( \WC_Order $wcOrder, \stdClass $result ): Order {
205 - $orderWeight = $this->parseFloat( $result->weight );
206 - if ( null === $orderWeight ) {
207 - $orderWeight = $this->calculator->calculateOrderWeight( $wcOrder );
208 - }
209 -
210 - $partialOrder = new Order(
211 - $result->id,
212 - $result->carrier_id
297 + public function getDataById( int $id ): ?object {
298 + return $this->wpdbAdapter->get_row(
299 + $this->wpdbAdapter->prepare(
300 + '
301 + SELECT `o`.* FROM `' . $this->wpdbAdapter->packeteryOrder . '` `o`
302 + ' . $this->getWcOrderJoinClause() . '
303 + WHERE `o`.`id` = %d',
304 + $id
305 + )
213 306 );
307 + }
214 308
215 - $partialOrder->setWeight( $orderWeight );
216 - $partialOrder->setPacketId( $result->packet_id );
217 - $partialOrder->setSize( new Size( $this->parseFloat( $result->length ), $this->parseFloat( $result->width ), $this->parseFloat( $result->height ) ) );
218 - $partialOrder->setIsExported( (bool) $result->is_exported );
219 - $partialOrder->setIsLabelPrinted( (bool) $result->is_label_printed );
220 - $partialOrder->setCarrierNumber( $result->carrier_number );
221 - $partialOrder->setPacketStatus( $result->packet_status );
222 - $partialOrder->setAdultContent( $this->containsAdultContent( $wcOrder ) );
223 - $partialOrder->setAddressValidated( (bool) $result->address_validated );
309 + private function getDataByWcOrder( WC_Order $wcOrder ): ?object {
310 + if ( ! ShippingProvider::wcOrderHasOurMethod( $wcOrder ) ) {
311 + return null;
312 + }
224 313
225 - if ( $result->delivery_address ) {
226 - $deliveryAddressDecoded = json_decode( $result->delivery_address );
227 - $deliveryAddress = new Address(
228 - $deliveryAddressDecoded->street,
229 - $deliveryAddressDecoded->city,
230 - $deliveryAddressDecoded->zip
231 - );
232 -
233 - $deliveryAddress->setHouseNumber( $deliveryAddressDecoded->houseNumber );
234 - $deliveryAddress->setLongitude( $deliveryAddressDecoded->longitude );
235 - $deliveryAddress->setLatitude( $deliveryAddressDecoded->latitude );
236 - $deliveryAddress->setCounty( $deliveryAddressDecoded->county );
237 -
238 - $partialOrder->setDeliveryAddress( $deliveryAddress );
314 + $packeteryOrderData = $this->getDataById( $wcOrder->get_id() );
315 + if ( ! is_object( $packeteryOrderData ) ) {
316 + return null;
239 317 }
240 318
241 - if ( null !== $result->point_id ) {
242 - $pickUpPoint = new PickupPoint(
243 - $result->point_id,
244 - $result->point_name,
245 - $result->point_city,
246 - $result->point_zip,
247 - $result->point_street,
248 - $result->point_url
249 - );
319 + return $packeteryOrderData;
320 + }
250 321
251 - $partialOrder->setPickupPoint( $pickUpPoint );
322 + /**
323 + * @throws InvalidCarrierException
324 + */
325 + public function getByWcOrder( WC_Order $wcOrder ): ?Order {
326 + $packeteryOrderData = $this->getDataByWcOrder( $wcOrder );
327 + if ( $packeteryOrderData === null ) {
328 + return null;
252 329 }
253 330
254 - return $partialOrder;
331 + return $this->builder->build( $wcOrder, $packeteryOrderData );
255 332 }
256 333
257 334 /**
258 - * Parses string value as float.
259 - *
260 - * @param string|float|null $value Value.
261 - *
262 - * @return float|null
335 + * Ignores InvalidCarrierException.
263 336 */
264 - private function parseFloat( $value ): ?float {
265 - if ( null === $value || '' === $value ) {
337 + public function getByWcOrderWithValidCarrier( WC_Order $wcOrder ): ?Order {
338 + $packeteryOrderData = $this->getDataByWcOrder( $wcOrder );
339 + if ( $packeteryOrderData === null ) {
266 340 return null;
267 341 }
268 342
269 - return (float) $value;
343 + try {
344 + return $this->builder->build( $wcOrder, $packeteryOrderData );
345 + } catch ( InvalidCarrierException $invalidCarrierException ) {
346 + return null;
347 + }
270 348 }
271 349
272 350 /**
273 351 * Transforms order to DB array.
@@ -273,41 +351,58 @@
273 351 * Transforms order to DB array.
274 352 *
275 353 * @param Order $order Order.
276 354 *
277 - * @return array
355 + * @return array<string, bool|float|int|string|null>
278 356 */
279 357 private function orderToDbArray( Order $order ): array {
280 358 $point = $order->getPickupPoint();
281 - if ( null === $point ) {
359 + if ( $point === null ) {
282 360 $point = new PickupPoint();
283 361 }
284 362
285 363 $deliveryAddress = null;
286 - if ( $order->isAddressValidated() && $order->getDeliveryAddress() ) {
364 + if ( $order->isAddressValidated() && $order->getDeliveryAddress() !== null ) {
287 365 $deliveryAddress = wp_json_encode( $order->getDeliveryAddress()->export() );
288 366 }
289 367
368 + $apiErrorDateTime = $order->getLastApiErrorDateTime();
369 + if ( $apiErrorDateTime !== null ) {
370 + $apiErrorDateTime = $apiErrorDateTime->format( CoreHelper::MYSQL_DATETIME_FORMAT );
371 + }
372 +
290 373 $data = [
291 - 'id' => (int) $order->getNumber(),
292 - 'carrier_id' => $order->getCarrierId(),
293 - 'is_exported' => (int) $order->isExported(),
294 - 'packet_id' => $order->getPacketId(),
295 - 'packet_status' => $order->getPacketStatus(),
296 - 'is_label_printed' => (int) $order->isLabelPrinted(),
297 - 'carrier_number' => $order->getCarrierNumber(),
298 - 'weight' => $order->getWeight(),
299 - 'point_id' => $point->getId(),
300 - 'point_name' => $point->getName(),
301 - 'point_url' => $point->getUrl(),
302 - 'point_street' => $point->getStreet(),
303 - 'point_zip' => $point->getZip(),
304 - 'point_city' => $point->getCity(),
305 - 'address_validated' => (int) $order->isAddressValidated(),
306 - 'delivery_address' => $deliveryAddress,
307 - 'length' => $order->getLength(),
308 - 'width' => $order->getWidth(),
309 - 'height' => $order->getHeight(),
374 + 'id' => (int) $order->getNumber(),
375 + 'carrier_id' => $order->getCarrier()->getId(),
376 + 'is_exported' => (int) $order->isExported(),
377 + 'packet_id' => $order->getPacketId(),
378 + 'packet_claim_id' => $order->getPacketClaimId(),
379 + 'packet_claim_password' => $order->getPacketClaimPassword(),
380 + 'consign_password' => $order->getConsignPassword(),
381 + 'packet_status' => $order->getPacketStatus(),
382 + 'stored_until' => $this->coreHelper->getStringFromDateTime( $order->getStoredUntil(), CoreHelper::DATEPICKER_FORMAT ),
383 + 'is_label_printed' => (int) $order->isLabelPrinted(),
384 + 'carrier_number' => $order->getCarrierNumber(),
385 + 'weight' => $order->getWeight(),
386 + 'car_delivery_id' => $order->getCarDeliveryId(),
387 + 'point_id' => $point->getId(),
388 + 'point_place' => $point->getPlace(),
389 + 'point_name' => $point->getName(),
390 + 'point_url' => $point->getUrl(),
391 + 'point_street' => $point->getStreet(),
392 + 'point_zip' => $point->getZip(),
393 + 'point_city' => $point->getCity(),
394 + 'address_validated' => (int) $order->isAddressValidated(),
395 + 'delivery_address' => $deliveryAddress,
396 + 'length' => $order->getLength(),
397 + 'width' => $order->getWidth(),
398 + 'height' => $order->getHeight(),
399 + 'adult_content' => $order->containsAdultContent(),
400 + 'cod' => $order->getManualCod(),
401 + 'value' => $order->getManualValue(),
402 + 'api_error_message' => $order->getLastApiErrorMessage(),
403 + 'api_error_date' => $apiErrorDateTime,
404 + 'deliver_on' => $this->coreHelper->getStringFromDateTime( $order->getDeliverOn(), CoreHelper::DATEPICKER_FORMAT ),
310 405 ];
311 406
312 407 return $data;
313 408 }
@@ -316,37 +411,106 @@
316 411 * Saves order.
317 412 *
318 413 * @param Order $order Order.
319 414 *
415 + * @return int|false The number of rows updated, or false on error.
416 + */
417 + public function save( Order $order ) {
418 + return $this->saveData( $this->orderToDbArray( $order ) );
419 + }
420 +
421 + /**
422 + * Saves order data.
423 + *
424 + * @param array<string, int|string|null|DateTimeImmutable> $orderData Order data.
425 + *
426 + * @return int|false The number of rows updated, or false on error.
427 + */
428 + public function saveData( array $orderData ) {
429 + $this->onBeforeDataInsertion( $orderData );
430 +
431 + return $this->wpdbAdapter->insertReplaceHelper( $this->wpdbAdapter->packeteryOrder, $orderData, null, 'REPLACE' );
432 + }
433 +
434 + /**
435 + * Calls logic before order data replace/insert.
436 + *
437 + * @param array<string, string|null|DateTimeImmutable> $orderData Order data.
320 438 * @return void
321 439 */
322 - public function save( Order $order ): void {
323 - $this->wpdb->_insert_replace_helper( $this->wpdb->packetery_order, $this->orderToDbArray( $order ), null, 'REPLACE' );
440 + private function onBeforeDataInsertion( array $orderData ): void {
441 + $pointId = $orderData['point_id'] ?? null;
442 + $carrierId = $orderData['carrier_id'] ?? null;
443 + /**
444 + * Tells if Packeta debug logs are enabled.
445 + *
446 + * @since 1.7.2
447 + */
448 + $isLoggingActive = (bool) apply_filters( 'packeta_enable_debug_logs', false );
449 +
450 + if ( ( $pointId !== null && $pointId !== '' ) || ! $isLoggingActive || ! $this->pickupPointsConfig->isInternalPickupPointCarrier( $carrierId ) ) {
451 + return;
452 + }
453 +
454 + $wcLogger = $this->wcAdapter->getLogger();
455 + $dataToLog = [
456 + 'order' => $orderData,
457 + 'trace' => array_map(
458 + static function ( array $item ): array {
459 + return [
460 + 'file' => sprintf( '%s(%s)', $item['file'], $item['line'] ),
461 + 'method' => sprintf( '%s%s%s(...)', $item['class'] ?? '', $item['type'] ?? '', $item['function'] ),
462 + ];
463 + },
464 + ( new Exception() )->getTrace()
465 + ),
466 + ];
467 + $wcLogger->warning(
468 + sprintf(
469 + 'Required value for the pickup point order is not set: %s',
470 + wp_json_encode( $dataToLog, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE )
471 + ),
472 + [ 'source' => 'packeta' ]
473 + );
324 474 }
325 475
326 476 /**
327 477 * Loads order entities by list of ids.
328 478 *
329 - * @param array $orderIds Order ids.
479 + * @param string[] $orderIds Order ids.
330 480 *
331 481 * @return Order[]
332 482 */
333 483 public function getByIds( array $orderIds ): array {
484 + if ( count( $orderIds ) === 0 ) {
485 + return [];
486 + }
487 +
488 + $wpdbAdapter = $this->wpdbAdapter;
489 + $ordersIdsPlaceholder = implode( ', ', array_fill( 0, count( $orderIds ), '%d' ) );
490 + $packeteryOrdersResult = $wpdbAdapter->get_results(
491 + $wpdbAdapter->prepare(
492 + 'SELECT * FROM `' . $wpdbAdapter->packeteryOrder . '`
493 + WHERE `id` IN (' . $ordersIdsPlaceholder . ')',
494 + $orderIds
495 + ),
496 + OBJECT_K
497 + );
498 +
334 499 $orderEntities = [];
335 - $posts = get_posts(
336 - [
337 - 'post_type' => 'shop_order',
338 - 'post__in' => $orderIds,
339 - 'post_status' => [ 'any', 'trash' ],
340 - 'nopaging' => true,
341 - ]
342 - );
343 - foreach ( $posts as $post ) {
344 - $wcOrder = wc_get_order( $post );
345 - $order = $this->getByWcOrder( $wcOrder );
346 - if ( $wcOrder && $order ) {
347 - $orderEntities[ $order->getNumber() ] = $order;
500 + foreach ( $orderIds as $orderId ) {
501 + if ( ! isset( $packeteryOrdersResult[ $orderId ] ) ) {
502 + continue;
348 503 }
504 +
505 + $wcOrder = $this->getWcOrderById( (int) $orderId );
506 + assert( $wcOrder !== null, 'WC order has to be present' );
507 +
508 + try {
509 + $orderEntities[ $orderId ] = $this->builder->build( $wcOrder, $packeteryOrdersResult[ $orderId ] );
510 + } catch ( InvalidCarrierException $exception ) {
511 + continue;
512 + }
349 513 }
350 514
351 515 return $orderEntities;
352 516 }
@@ -353,83 +517,238 @@
353 517
354 518 /**
355 519 * Finds orders.
356 520 *
357 - * @param int $limit Number of records.
521 + * @param string[] $allowedPacketStatuses Allowed packet statuses.
522 + * @param string[] $allowedOrderStatuses Allowed order statuses.
523 + * @param int $maxDays Max number of days of single packet sync.
524 + * @param int $limit Number of records.
358 525 *
359 - * @return iterable|Order[]
526 + * @return \Generator<int>
527 + * @throws Exception Exception.
360 528 */
361 - public function findStatusSyncingOrders( int $limit ): iterable {
362 - $wpdb = $this->wpdb;
529 + public function findStatusSyncingOrderIds( array $allowedPacketStatuses, array $allowedOrderStatuses, int $maxDays, int $limit ): \Generator {
530 + $dateLimit = CoreHelper::now()->modify( '- ' . $maxDays . ' days' )->format( 'Y-m-d H:i:s' );
363 531
364 - $rows = $wpdb->get_results(
365 - $wpdb->prepare(
366 - '
367 - SELECT o.* FROM `' . $wpdb->packetery_order . '` o
368 - JOIN `' . $wpdb->posts . '` wp_p ON wp_p.`ID` = o.`id`
369 - WHERE ( o.`packet_status` IS NULL OR o.`packet_status` NOT IN ("delivered", "returned", "cancelled") ) AND o.`packet_id` IS NOT NULL
370 - ORDER BY wp_p.`post_date`
532 + $andWhere = [ '`o`.`packet_id` IS NOT NULL' ];
533 + $hposEnabled = ModuleHelper::isHposEnabled();
534 +
535 + if ( $hposEnabled ) {
536 + $andWhere[] = $this->wpdbAdapter->prepare( '`wc_o`.`date_created_gmt` >= %s', $dateLimit );
537 + } else {
538 + $andWhere[] = $this->wpdbAdapter->prepare( '`wp_p`.`post_date_gmt` >= %s', $dateLimit );
539 + }
540 +
541 + $orPacketStatus = [];
542 + $orPacketStatus[] = '`o`.`packet_status` IS NULL';
543 +
544 + if ( count( $allowedPacketStatuses ) > 0 ) {
545 + $orPacketStatus[] = '`o`.`packet_status` IN (' . $this->wpdbAdapter->prepareInClause( $allowedPacketStatuses ) . ')';
546 + }
547 +
548 + if ( count( $orPacketStatus ) > 0 ) {
549 + $andWhere[] = '(' . implode( ' OR ', $orPacketStatus ) . ')';
550 + }
551 +
552 + if ( count( $allowedOrderStatuses ) > 0 && $hposEnabled ) {
553 + $andWhere[] = '`wc_o`.`status` IN (' . $this->wpdbAdapter->prepareInClause( $allowedOrderStatuses ) . ')';
554 + } elseif ( count( $allowedOrderStatuses ) > 0 && $hposEnabled === false ) {
555 + $andWhere[] = '`wp_p`.`post_status` IN (' . $this->wpdbAdapter->prepareInClause( $allowedOrderStatuses ) . ')';
556 + } else {
557 + $andWhere[] = '1 = 0';
558 + }
559 +
560 + $where = '';
561 + if ( count( $andWhere ) > 0 ) {
562 + $where = ' WHERE ' . implode( ' AND ', $andWhere );
563 + }
564 +
565 + if ( $hposEnabled ) {
566 + $orderBy = ' ORDER BY `wc_o`.`date_created_gmt` ';
567 + } else {
568 + $orderBy = ' ORDER BY `wp_p`.`post_date_gmt` ';
569 + }
570 +
571 + $sql = $this->wpdbAdapter->prepare(
572 + '
573 + SELECT `o`.`id` FROM `' . $this->wpdbAdapter->packeteryOrder . '` `o`
574 + ' . $this->getWcOrderJoinClause() . '
575 + ' . $where . '
576 + ' . $orderBy . '
371 577 LIMIT %d',
372 - $limit
373 - )
578 + $limit
374 579 );
375 580
581 + $rows = $this->wpdbAdapter->get_results( $sql );
582 +
376 583 foreach ( $rows as $row ) {
377 - $wcOrder = wc_get_order( $row->id );
378 - if ( ! $wcOrder->has_shipping_method( ShippingMethod::PACKETERY_METHOD_ID ) ) {
584 + $wcOrder = $this->getWcOrderById( (int) $row->id );
585 + if ( $wcOrder === null || ! ShippingProvider::wcOrderHasOurMethod( $wcOrder ) ) {
379 586 continue;
380 587 }
381 588
382 - $partial = $this->createPartialOrder( $wcOrder, $row );
383 - yield $this->builder->finalize( $wcOrder, $partial );
589 + yield (int) $row->id;
384 590 }
385 591 }
386 592
387 593 /**
388 - * Finds out if adult content is present.
594 + * Counts order to be submitted.
389 595 *
390 - * @param \WC_Order $wcOrder WC Order.
596 + * @return int
597 + */
598 + public function countOrdersToSubmit(): int {
599 + return $this->countOrders( [ 'packetery_to_submit' => '1' ] );
600 + }
601 +
602 + /**
603 + * Counts order to be printed.
391 604 *
392 - * @return bool
605 + * @return int
393 606 */
394 - private function containsAdultContent( \WC_Order $wcOrder ): bool {
395 - foreach ( $wcOrder->get_items() as $item ) {
396 - $itemData = $item->get_data();
397 - $productEntity = Product\Entity::fromPostId( $itemData['product_id'] );
398 - if ( $productEntity->isAgeVerification18PlusRequired() ) {
399 - return true;
400 - }
607 + public function countOrdersToPrint(): int {
608 + return $this->countOrders( [ 'packetery_to_print' => '1' ] );
609 + }
610 +
611 + /**
612 + * Counts orders by given params.
613 + *
614 + * @param array<string, string> $params Params.
615 + *
616 + * @return int
617 + */
618 + private function countOrders( array $params ): int {
619 + $clauses = [ 'join' => '' ];
620 +
621 + if ( ModuleHelper::isHposEnabled() ) {
622 + $clauses['select'] = ' SELECT COUNT(DISTINCT `' . $this->wpdbAdapter->wcOrders . '`.`id`) ';
623 + $clauses['from'] = ' FROM `' . $this->wpdbAdapter->wcOrders . '` ';
624 + } else {
625 + $clauses['select'] = ' SELECT COUNT(DISTINCT `' . $this->wpdbAdapter->posts . '`.`ID`) ';
626 + $clauses['from'] = ' FROM `' . $this->wpdbAdapter->posts . '` ';
401 627 }
402 628
403 - return false;
629 + $clauses['where'] = ' WHERE `' . $this->wpdbAdapter->packeteryOrder . '`.`id` IS NOT NULL ';
630 + $clauses = $this->processClauses( $clauses, null, $params );
631 +
632 + return (int) $this->wpdbAdapter->get_var(
633 + $clauses['select'] .
634 + $clauses['from'] .
635 + $clauses['join'] .
636 + $clauses['where']
637 + );
404 638 }
405 639
406 640 /**
407 - * Counts order to be submitted.
641 + * Gets WC Order join clause.
408 642 *
409 - * @return int
643 + * @return string
410 644 */
411 - public function countOrdersToSubmit(): int {
412 - $wpdb = $this->wpdb;
645 + public function getWcOrderJoinClause(): string {
646 + $packeteryTableAlias = 'o';
413 647
414 - return (int) $wpdb->get_var(
415 - 'SELECT COUNT(DISTINCT o.id) FROM `' . $wpdb->packetery_order . '` o
416 - JOIN `' . $wpdb->posts . '` wp_p ON wp_p.`ID` = o.`id`
417 - WHERE o.`carrier_id` IS NOT NULL AND o.`is_exported` = false'
648 + if ( ModuleHelper::isHposEnabled() ) {
649 + $sourceTableAlias = 'wc_o';
650 +
651 + return sprintf(
652 + 'JOIN `%s` `%s` ON `%s`.`id` = `%s`.`id`',
653 + $this->wpdbAdapter->wcOrders,
654 + $sourceTableAlias,
655 + $sourceTableAlias,
656 + $packeteryTableAlias
657 + );
658 + }
659 +
660 + $sourceTableAlias = 'wp_p';
661 +
662 + return sprintf(
663 + 'JOIN `%s` `%s` ON `%s`.`ID` = `%s`.`id`',
664 + $this->wpdbAdapter->posts,
665 + $sourceTableAlias,
666 + $sourceTableAlias,
667 + $packeteryTableAlias
418 668 );
419 669 }
420 670
421 671 /**
422 - * Counts order to be printed.
672 + * Deletes all custom table records linked to permanently deleted orders.
423 673 *
424 - * @return int
674 + * @return void
425 675 */
426 - public function countOrdersToPrint(): int {
427 - $wpdb = $this->wpdb;
676 + public function deleteOrphans(): void {
677 + if ( ModuleHelper::isHposEnabled() ) {
678 + $this->wpdbAdapter->query(
679 + 'DELETE `' . $this->wpdbAdapter->packeteryOrder . '` FROM `' . $this->wpdbAdapter->packeteryOrder . '`
680 + LEFT JOIN `' . $this->wpdbAdapter->wcOrders . '` ON `' . $this->wpdbAdapter->wcOrders . '`.`id` = `' . $this->wpdbAdapter->packeteryOrder . '`.`id`
681 + WHERE `' . $this->wpdbAdapter->wcOrders . '`.`id` IS NULL'
682 + );
428 683
429 - return (int) $wpdb->get_var(
430 - 'SELECT COUNT(DISTINCT o.id) FROM `' . $wpdb->packetery_order . '` o
431 - JOIN `' . $wpdb->posts . '` wp_p ON wp_p.`ID` = o.`id`
432 - WHERE o.`packet_id` IS NOT NULL AND o.`is_label_printed` = false'
684 + return;
685 + }
686 +
687 + $this->wpdbAdapter->query(
688 + 'DELETE `' . $this->wpdbAdapter->packeteryOrder . '` FROM `' . $this->wpdbAdapter->packeteryOrder . '`
689 + LEFT JOIN `' . $this->wpdbAdapter->posts . '` ON `' . $this->wpdbAdapter->posts . '`.`ID` = `' . $this->wpdbAdapter->packeteryOrder . '`.`id`
690 + WHERE `' . $this->wpdbAdapter->posts . '`.`ID` IS NULL'
433 691 );
692 + }
693 +
694 + /**
695 + * Deletes order data including customs declaration and its items from custom tables.
696 + *
697 + * @param int $orderId Order id.
698 + *
699 + * @return bool true on success, false in case of db failure.
700 + */
701 + public function delete( int $orderId ): bool {
702 + try {
703 + $this->customsDeclarationRepository->delete( (string) $orderId );
704 + $this->wpdbAdapter->delete( $this->wpdbAdapter->packeteryOrder, [ 'id' => $orderId ], '%d' );
705 +
706 + return true;
707 + } catch ( DeleteErrorException $e ) {
708 + return false;
709 + }
710 + }
711 +
712 + /**
713 + * Fires after post deletion.
714 + *
715 + * @param int|mixed $postId Post id.
716 + * @param WP_Post|mixed $post Post object.
717 + *
718 + * @return void
719 + */
720 + public function deletedPostHook( $postId, $post ): void {
721 + if ( ! is_int( $postId ) ) {
722 + WcLogger::logArgumentTypeError( __METHOD__, 'postId', 'int', $postId );
723 +
724 + return;
725 + }
726 +
727 + if ( ! $post instanceof WP_Post ) {
728 + WcLogger::logArgumentTypeError( __METHOD__, 'post', WP_Post::class, $post );
729 +
730 + return;
731 + }
732 +
733 + // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
734 + if ( $post->post_type === 'shop_order' ) {
735 + $this->delete( $postId );
736 + }
737 + }
738 +
739 + /**
740 + * Gets WC_Order object.
741 + *
742 + * @param int $id Order id.
743 + *
744 + * @return WC_Order|null
745 + */
746 + public function getWcOrderById( int $id ): ?WC_Order {
747 + $result = wc_get_order( $id );
748 + if ( $result instanceof WC_Order ) {
749 + return $result;
750 + }
751 +
752 + return null;
434 753 }
435 754 }