PluginProbe
Packeta / 2.1
Packeta v2.1
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.1, at src/Packetery/Module/Order/CustomsDeclarationMetabox.php

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