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
packeta / src / Packetery / Module / Order / CustomsDeclarationMetabox.php

CustomsDeclarationMetabox.php in Packeta 2.3.2, at src/Packetery/Module/Order/CustomsDeclarationMetabox.php

596 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class CustomsDeclarationMetabox.
4 *
5 * @package Packetery
6 */
7
8 declare(strict_types=1);
9
10 namespace Packetery\Module\Order;
11
12 use Packetery\Core\CoreHelper;
13 use Packetery\Core\Entity;
14 use Packetery\Core\Entity\Order;
15 use Packetery\Latte\Engine;
16 use Packetery\Module\CustomsDeclaration;
17 use Packetery\Module\EntityFactory;
18 use Packetery\Module\Exception\DeleteErrorException;
19 use Packetery\Module\FormFactory;
20 use Packetery\Module\FormRules;
21 use Packetery\Module\Framework\WpAdapter;
22 use Packetery\Module\Message;
23 use Packetery\Module\MessageManager;
24 use Packetery\Module\ModuleHelper;
25 use Packetery\Nette\Forms\Container;
26 use Packetery\Nette\Forms\Controls\BaseControl;
27 use Packetery\Nette\Forms\Controls\Checkbox;
28 use Packetery\Nette\Forms\Controls\UploadControl;
29 use Packetery\Nette\Forms\Form;
30 use Packetery\Nette\Http\FileUpload;
31 use Packetery\Nette\Http\Request;
32
33 /**
34 * Class CustomsDeclarationMetabox.
35 */
36 class CustomsDeclarationMetabox {
37
38 private const MAX_UPLOAD_FILE_MEGABYTES = 16;
39
40 private const EAD_OWN = 'own';
41 private const EAD_CREATE = 'create';
42 private const EAD_CARRIER = 'carrier';
43
44 public const FORM_ID = 'packetery-customs-declaration-metabox-form';
45 public const FORM_CONTAINER_NAME = 'packetery_customs_declaration';
46 public const FORM_ACTIVATOR_NAME = 'fill_customs_declaration';
47
48 /**
49 * Latte engine.
50 *
51 * @var Engine
52 */
53 private $latteEngine;
54
55 /**
56 * Form factory.
57 *
58 * @var FormFactory
59 */
60 private $formFactory;
61
62 /**
63 * Customs declaration repository.
64 *
65 * @var CustomsDeclaration\Repository
66 */
67 private $customsDeclarationRepository;
68
69 /**
70 * Customs declaration entity factory.
71 *
72 * @var EntityFactory\CustomsDeclaration
73 */
74 private $customsDeclarationEntityFactory;
75
76 /**
77 * HTTP Request.
78 *
79 * @var Request
80 */
81 private $request;
82
83 /**
84 * Message manager.
85 *
86 * @var MessageManager
87 */
88 private $messageManager;
89
90 /**
91 * Order detail comon logic.
92 *
93 * @var DetailCommonLogic
94 */
95 private $detailCommonLogic;
96
97 /**
98 * @var WpAdapter
99 */
100 private $wpAdapter;
101
102 /**
103 * Constructor.
104 *
105 * @param Engine $latteEngine Latte engine.
106 * @param FormFactory $formFactory Form factory.
107 * @param CustomsDeclaration\Repository $customsDeclarationRepository Customs declaration repository.
108 * @param EntityFactory\CustomsDeclaration $customsDeclarationEntityFactory Customs declaration entity factory.
109 * @param Request $request Request.
110 * @param MessageManager $messageManager Message manager.
111 * @param DetailCommonLogic $detailCommonLogic Detail common logic.
112 */
113 public function __construct(
114 Engine $latteEngine,
115 FormFactory $formFactory,
116 CustomsDeclaration\Repository $customsDeclarationRepository,
117 EntityFactory\CustomsDeclaration $customsDeclarationEntityFactory,
118 Request $request,
119 MessageManager $messageManager,
120 DetailCommonLogic $detailCommonLogic,
121 WpAdapter $wpAdapter
122 ) {
123 $this->latteEngine = $latteEngine;
124 $this->formFactory = $formFactory;
125 $this->customsDeclarationRepository = $customsDeclarationRepository;
126 $this->customsDeclarationEntityFactory = $customsDeclarationEntityFactory;
127 $this->request = $request;
128 $this->messageManager = $messageManager;
129 $this->detailCommonLogic = $detailCommonLogic;
130 $this->wpAdapter = $wpAdapter;
131 }
132
133 /**
134 * Registers related hooks.
135 *
136 * @return void
137 */
138 public function register(): void {
139 add_action( 'add_meta_boxes', [ $this, 'addMetaBoxes' ] );
140 add_action( 'admin_head', [ $this, 'renderTemplate' ] );
141 }
142
143 /**
144 * Adds meta boxes.
145 *
146 * @return void
147 */
148 public function addMetaBoxes(): void {
149 $order = $this->detailCommonLogic->getOrder();
150
151 if (
152 $order === null ||
153 $order->getCarrier()->requiresCustomsDeclarations() === false
154 ) {
155 return;
156 }
157
158 add_meta_box(
159 'packetery_customs_declaration_metabox',
160 __( 'Customs declaration', 'packeta' ),
161 [ $this, 'render' ],
162 ModuleHelper::isHposEnabled() ? wc_get_page_screen_id( 'shop-order' ) : 'shop_order',
163 'advanced',
164 'high'
165 );
166 }
167
168 /**
169 * Renders template.
170 *
171 * @return void
172 */
173 public function renderTemplate(): void {
174 $formTemplate = $this->formFactory->create();
175 $prefixContainer = $formTemplate->addContainer( self::FORM_CONTAINER_NAME );
176 $items = $prefixContainer->addContainer( 'items' );
177 $this->addCustomsDeclarationItem( $formTemplate->addCheckbox( self::FORM_ACTIVATOR_NAME ), $items, '0' );
178
179 $this->latteEngine->render(
180 PACKETERY_PLUGIN_DIR . '/template/order/customs-declaration-form-template.latte',
181 [
182 'formTemplate' => $formTemplate,
183 'translations' => [
184 'delete' => __( 'Delete', 'packeta' ),
185 ],
186 ]
187 );
188 }
189
190 /**
191 * Saves submitted form fields data.
192 *
193 * @param Order $order Order ID.
194 * @return void
195 */
196 public function saveFields( Order $order ): void {
197 if (
198 ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ||
199 $this->request->getPost( self::FORM_CONTAINER_NAME ) === null
200 ) {
201 return;
202 }
203
204 $form = $this->createForm(
205 $this->request->getPost(),
206 $this->customsDeclarationRepository->getByOrderNumber( $order->getNumber() )
207 );
208
209 if ( $form->isSubmitted() ) {
210 $form->fireEvents();
211 }
212 }
213
214 /**
215 * Renders meta box.
216 *
217 * @return void
218 */
219 public function render(): void {
220 $order = $this->detailCommonLogic->getOrder();
221 if ( $order === null ) {
222 return;
223 }
224
225 $customsDeclaration = $this->customsDeclarationRepository->getByOrderNumber( $order->getNumber() );
226
227 $formData = [];
228 if ( $customsDeclaration !== null ) {
229 $formData = [
230 self::FORM_CONTAINER_NAME => $this->customsDeclarationRepository->declarationToDbArray(
231 $customsDeclaration,
232 [ 'invoice_file', 'ead_file', 'order_id' ]
233 ),
234 ];
235 $formData[ self::FORM_CONTAINER_NAME ]['items'] = [];
236
237 $customsDeclarationItems = $this->customsDeclarationRepository->getItemsByCustomsDeclarationId( $customsDeclaration->getId() );
238 foreach ( $customsDeclarationItems as $customsDeclarationItem ) {
239 $formData[ self::FORM_CONTAINER_NAME ]['items'][ $customsDeclarationItem->getId() ] = $this->customsDeclarationRepository->declarationItemToDbArray( $customsDeclarationItem );
240 }
241 }
242
243 $form = $this->createForm( $formData, $customsDeclaration );
244 $form->setDefaults( $formData );
245
246 $hasInvoiceFile = $customsDeclaration !== null && $customsDeclaration->hasInvoiceFileContent();
247 $hasEadFile = $customsDeclaration !== null && $customsDeclaration->hasEadFileContent();
248 $runWizardUrl = $this->wpAdapter->adminUrl( "admin.php?page=wc-orders&action=edit&id={$order->getNumber()}&wizard-enabled=true&wizard-order-detail-custom-declaration-enabled=true#packetery_customs_declaration_metabox" );
249
250 $this->latteEngine->render(
251 PACKETERY_PLUGIN_DIR . '/template/order/customs-declaration-metabox.latte',
252 [
253 'form' => $form,
254 'hasInvoiceFile' => $hasInvoiceFile,
255 'hasEadFile' => $hasEadFile,
256 'runWizardUrl' => $runWizardUrl,
257 'translations' => [
258 'addCustomsDeclarationItem' => $this->wpAdapter->__( 'Add item', 'packeta' ),
259 'delete' => $this->wpAdapter->__( 'Delete', 'packeta' ),
260 'itemsLabel' => $this->wpAdapter->__( 'Items', 'packeta' ),
261 'fileUploaded' => $this->wpAdapter->__( 'File uploaded.', 'packeta' ),
262 'runWizard' => $this->wpAdapter->__( 'Run customs declaration wizard', 'packeta' ),
263 ],
264 ]
265 );
266 }
267
268 /**
269 * Creates form.
270 *
271 * @param array $structureData Data specifying how form will be constructed.
272 * When user browser sends added customs declaration items, the form factory has to reflect that.
273 * @param Entity\CustomsDeclaration|null $customsDeclaration Related customs declaration.
274 *
275 * @return Form
276 */
277 private function createForm( array $structureData, ?Entity\CustomsDeclaration $customsDeclaration ): Form {
278 $form = $this->formFactory->create();
279
280 $activator = $form->addCheckbox( self::FORM_ACTIVATOR_NAME, __( 'View/hide customs declaration form', 'packeta' ) );
281 if ( $this->request->getQuery( 'wizard-order-detail-custom-declaration-enabled' ) === 'true' ) {
282 $activator->setValue( true );
283 }
284 $activator
285 ->addCondition( Form::FILLED )
286 ->toggle( 'customs-declaration-container' );
287
288 $prefixContainer = $form->addContainer( self::FORM_CONTAINER_NAME );
289
290 $ead = $prefixContainer->addSelect(
291 'ead',
292 __( 'EAD', 'packeta' ),
293 [
294 self::EAD_OWN => __( 'Self-declaration (I have a EAD)', 'packeta' ),
295 self::EAD_CREATE => __( 'Issuing a EAD via Packeta', 'packeta' ),
296 self::EAD_CARRIER => __( 'Postal clearance (no EAD and no fees)', 'packeta' ),
297 ]
298 );
299 $ead
300 ->addConditionOn( $activator, Form::FILLED )
301 ->setRequired();
302
303 $prefixContainer->addText( 'delivery_cost', __( 'Delivery cost', 'packeta' ) )
304 ->addConditionOn( $activator, Form::FILLED )
305 ->setRequired()
306 ->addRule( Form::FLOAT )
307 ->addRule( ...FormRules::getGreaterThanParameters( 0 ) );
308
309 $prefixContainer->addText( 'invoice_number', __( 'Invoice number', 'packeta' ) )
310 ->addConditionOn( $activator, Form::FILLED )
311 ->setRequired();
312
313 $prefixContainer->addText( 'invoice_issue_date', __( 'Invoice issue date', 'packeta' ) )
314 ->addConditionOn( $activator, Form::FILLED )
315 ->setRequired()
316 ->addRule( ...FormRules::getDateParameters() );
317
318 $invoiceFile = $prefixContainer->addUpload( 'invoice_file', __( 'Invoice PDF file', 'packeta' ) )
319 ->setRequired( false );
320
321 if ( $customsDeclaration === null || $customsDeclaration->hasInvoiceFileContent() === false ) {
322 $invoiceFile
323 ->addConditionOn( $activator, Form::FILLED )
324 ->addConditionOn( $ead, Form::EQUAL, self::EAD_OWN )
325 ->setRequired()
326 ->endCondition()
327 ->addConditionOn( $ead, Form::EQUAL, self::EAD_CREATE )
328 ->setRequired();
329 }
330
331 $prefixContainer->addText( 'mrn', __( 'MRN', 'packeta' ) )
332 ->setRequired( false )
333 ->addConditionOn( $activator, Form::FILLED )
334 ->addRule( Form::MAX_LENGTH, null, 32 )
335 ->addConditionOn( $ead, Form::EQUAL, self::EAD_OWN )
336 ->toggle( 'customs-declaration-own-field-mrn' )
337 ->setRequired();
338
339 $eadFile = $prefixContainer->addUpload( 'ead_file', __( 'EAD PDF file', 'packeta' ) )
340 ->setRequired( false )
341 ->addConditionOn( $ead, Form::EQUAL, self::EAD_OWN )
342 ->toggle( 'customs-declaration-own-field-ead_file' );
343
344 if ( $customsDeclaration === null || $customsDeclaration->hasEadFileContent() === false ) {
345 $eadFile
346 ->addConditionOn( $activator, Form::FILLED )
347 ->addConditionOn( $ead, Form::EQUAL, self::EAD_OWN )
348 ->setRequired();
349 }
350
351 $form->addSubmit( 'save' );
352
353 $items = $prefixContainer->addContainer( 'items' );
354
355 $itemsData = $structureData[ self::FORM_CONTAINER_NAME ]['items'] ?? null;
356
357 if ( $itemsData === null ) {
358 $this->addCustomsDeclarationItem( $activator, $items, 'new_0' );
359 } else {
360 foreach ( $itemsData as $itemId => $itemDefaults ) {
361 $this->addCustomsDeclarationItem( $activator, $items, (string) $itemId );
362 }
363 }
364
365 $form->onSuccess[] = [ $this, 'onFormSuccess' ];
366 $form->onError[] = [ $this, 'onFormError' ];
367
368 return $form;
369 }
370
371 /**
372 * On form error.
373 *
374 * @param Form $form Form.
375 * @return void
376 */
377 public function onFormError( Form $form ): void {
378 /** Form input control. @var BaseControl[] $controls */
379 $controls = $form->getComponents( true, BaseControl::class );
380 foreach ( $controls as $control ) {
381 if ( $control instanceof BaseControl ) {
382 foreach ( $control->getErrors() as $error ) {
383 $this->messageManager->flashMessageObject(
384 Message::create()
385 ->setText( sprintf( '%s: %s', $control->getCaption(), $error ) )
386 ->setType( MessageManager::TYPE_ERROR )
387 );
388 }
389 }
390 }
391 }
392
393 /**
394 * On form success callback.
395 *
396 * @param Form $form Form.
397 * @return void
398 */
399 public function onFormSuccess( Form $form ): void {
400 $order = $this->detailCommonLogic->getOrder();
401 if ( $order === null ) {
402 return;
403 }
404
405 $fieldsToOmit = [];
406 /** @var Container $customsDeclarationContainer */
407 $customsDeclarationContainer = $form[ self::FORM_CONTAINER_NAME ];
408 $prefixedValues = $form->getValues( 'array' );
409 $containerValues = $prefixedValues[ self::FORM_CONTAINER_NAME ];
410 $items = $containerValues['items'];
411 unset( $containerValues['items'] );
412
413 if ( $prefixedValues[ self::FORM_ACTIVATOR_NAME ] === false ) {
414 return;
415 }
416
417 $this->processUploadedFile(
418 'invoice_file',
419 'invoice_file_id',
420 $containerValues,
421 $customsDeclarationContainer,
422 $fieldsToOmit
423 );
424
425 $this->processUploadedFile(
426 'ead_file',
427 'ead_file_id',
428 $containerValues,
429 $customsDeclarationContainer,
430 $fieldsToOmit
431 );
432
433 if ( $containerValues['mrn'] === '' ) {
434 $containerValues['mrn'] = null;
435 }
436
437 $containerValues['id'] = null;
438 $oldCustomsDeclaration = $this->customsDeclarationRepository->getByOrderNumber( $order->getNumber() );
439 if ( $oldCustomsDeclaration !== null ) {
440 $containerValues['id'] = $oldCustomsDeclaration->getId();
441 }
442
443 $customsDeclaration = $this->customsDeclarationEntityFactory->fromStandardizedStructure( $containerValues, $order->getNumber() );
444 $customsDeclaration->setInvoiceFile( $containerValues['invoice_file'], (bool) $containerValues['invoice_file'] );
445 $customsDeclaration->setEadFile( $containerValues['ead_file'], (bool) $containerValues['ead_file'] );
446 $updatedRowCount = $this->customsDeclarationRepository->save( $customsDeclaration, $fieldsToOmit );
447 if ( $updatedRowCount === false ) {
448 $this->messageManager->flash_message(
449 (string) $this->wpAdapter->__( 'An error occurred while saving the customs declaration. More details in WC log.', 'packeta' ),
450 MessageManager::TYPE_ERROR
451 );
452 }
453
454 $customsDeclarationItems = $this->customsDeclarationRepository->getItemsByCustomsDeclarationId( $customsDeclaration->getId() );
455 $customsDeclaration->setItems( $customsDeclarationItems );
456 foreach ( $customsDeclarationItems as $customsDeclarationItem ) {
457 $itemId = $customsDeclarationItem->getId();
458 if ( ! isset( $items[ $itemId ] ) ) {
459 try {
460 $this->customsDeclarationRepository->deleteItem( (int) $itemId );
461 } catch ( DeleteErrorException $e ) {
462 // No user message needed.
463 }
464 }
465 }
466
467 $itemSavingError = false;
468 foreach ( $items as $itemId => $item ) {
469 if ( strpos( (string) $itemId, 'new_' ) === 0 ) {
470 $itemId = null;
471 } else {
472 $itemId = (string) $itemId;
473 }
474
475 $item['id'] = $itemId;
476 $item['customs_declaration_id'] = $customsDeclaration->getId();
477 $updatedRowCount = $this->customsDeclarationRepository->saveItem(
478 $this->customsDeclarationEntityFactory->createItemFromStandardizedStructure( $item )
479 );
480 if ( $updatedRowCount === false ) {
481 $itemSavingError = true;
482 }
483 }
484
485 if ( $itemSavingError === true ) {
486 $this->messageManager->flash_message(
487 (string) $this->wpAdapter->__( 'An error occurred while saving the customs declaration items. More details in WC log.', 'packeta' ),
488 MessageManager::TYPE_ERROR
489 );
490 }
491 }
492
493 /**
494 * Adds customs declaration item.
495 *
496 * @param Checkbox $activator Activating checkbox.
497 * @param Container $container Container.
498 * @param string $index Item index.
499 *
500 * @return void
501 */
502 public function addCustomsDeclarationItem( Checkbox $activator, Container $container, string $index ): void {
503 $item = $container->addContainer( $index );
504 $item->addText( 'customs_code', __( 'Customs code', 'packeta' ) )
505 ->addConditionOn( $activator, Form::FILLED )
506 ->setRequired()
507 ->addRule( Form::MAX_LENGTH, null, 8 );
508
509 $item->addText( 'value', __( 'Value', 'packeta' ) )
510 ->addConditionOn( $activator, Form::FILLED )
511 ->setRequired()
512 ->addRule( Form::FLOAT )
513 ->addRule( ...FormRules::getGreaterThanParameters( 0 ) );
514
515 $item->addText( 'product_name_en', __( 'Product name (EN)', 'packeta' ) )
516 ->addConditionOn( $activator, Form::FILLED )
517 ->setRequired();
518 $item->addText( 'product_name', __( 'Product name', 'packeta' ) );
519
520 $item->addText( 'units_count', __( 'Units count', 'packeta' ) )
521 ->addConditionOn( $activator, Form::FILLED )
522 ->setRequired()
523 ->addRule( Form::INTEGER )
524 ->addRule( ...FormRules::getGreaterThanParameters( 0 ) );
525
526 $item->addText( 'country_of_origin', __( 'Country of origin code', 'packeta' ) )
527 ->addConditionOn( $activator, Form::FILLED )
528 ->setRequired()
529 ->addRule( Form::LENGTH, null, 2 );
530
531 $item->addText( 'weight', __( 'Weight (kg)', 'packeta' ) )
532 ->addConditionOn( $activator, Form::FILLED )
533 ->setRequired()
534 ->addRule( Form::FLOAT )
535 ->addRule( ...FormRules::getGreaterThanParameters( 0 ) )
536 ->addFilter(
537 static function ( float $value ): float {
538 return CoreHelper::simplifyWeight( $value );
539 }
540 )
541 ->addRule( ...FormRules::getGreaterThanParameters( 0 ) );
542
543 $item->addCheckbox( 'is_food_or_book', __( 'Food or book?', 'packeta' ) );
544 $item->addCheckbox( 'is_voc', __( 'Is VOC?', 'packeta' ) );
545 }
546
547 /**
548 * Handle file upload.
549 *
550 * @param string $key File key.
551 * @param string $relatedFileIdKey Related file id key.
552 * @param array $containerValues Container values.
553 * @param Container $formContainer Form container.
554 * @param string[] $fieldsToOmit Fields to omit.
555 *
556 * @return void
557 */
558 private function processUploadedFile( string $key, string $relatedFileIdKey, array &$containerValues, Container $formContainer, array &$fieldsToOmit ): void {
559 $fileUpload = $containerValues[ $key ];
560 $uploadControl = $formContainer[ $key ];
561
562 if ( $uploadControl instanceof UploadControl ) {
563 if ( $fileUpload->hasFile() && $fileUpload->getSize() <= 0 ) {
564 $uploadControl->addError( __( 'Uploaded file is empty.', 'packeta' ) );
565 $fileUpload = new FileUpload( null );
566 }
567
568 if ( $fileUpload->hasFile() && self::MAX_UPLOAD_FILE_MEGABYTES * 1024 * 1024 === $fileUpload->getSize() ) {
569 // translators: %d is numeric value.
570 $uploadControl->addError( sprintf( __( 'Uploaded file is too big for storage. Max size is %d MB.', 'packeta' ), self::MAX_UPLOAD_FILE_MEGABYTES ) );
571 $fileUpload = new FileUpload( null );
572 }
573
574 if ( $fileUpload->hasFile() && $fileUpload->isOk() ) {
575 $containerValues[ $key ] = static function () use ( $fileUpload ): string {
576 return $fileUpload->getContents();
577 };
578 $containerValues[ $relatedFileIdKey ] = null;
579 }
580
581 if ( $fileUpload->hasFile() && $fileUpload->isOk() === false ) {
582 $containerValues[ $key ] = null;
583 $containerValues[ $relatedFileIdKey ] = null;
584 $uploadControl->addError( __( 'File failed to upload.', 'packeta' ) );
585 }
586
587 if ( $containerValues[ $key ] instanceof FileUpload ) {
588 $containerValues[ $key ] = null;
589 $containerValues[ $relatedFileIdKey ] = null;
590 $fieldsToOmit[] = $key;
591 $fieldsToOmit[] = $relatedFileIdKey;
592 }
593 }
594 }
595 }
596