PluginProbe
Packeta / 1.6.0
Packeta v1.6.0
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 1.6.0, at src/Packetery/Module/Order/CustomsDeclarationMetabox.php

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