src/Form/CreateOrEditProfileForm.php line 180

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-04-29
  5.  * Time: 18:49
  6.  */
  7. namespace App\Form;
  8. use AngelGamez\TranslatableBundle\Entity\TranslatableValue;
  9. use App\Entity\ClientTypes;
  10. use App\Entity\Location\City;
  11. use App\Entity\Location\Station;
  12. use App\Entity\Profile\Confirmation\ApprovalRequest;
  13. use App\Entity\Profile\Genders;
  14. use App\Entity\Profile\PersonParameters;
  15. use App\Entity\Profile\Photo;
  16. use App\Entity\Profile\Profile;
  17. use App\Entity\Profile\ProfileService;
  18. use App\Entity\Profile\Video;
  19. use App\Entity\Service;
  20. use App\Entity\ServiceGroups;
  21. use App\Event\UploadedFileModified;
  22. use App\Form\Type\ApartmentsPricingType;
  23. use App\Form\Type\CarPricingType;
  24. use App\Form\Type\ClientRestrictionsType;
  25. use App\Form\Type\ExpressPricingType;
  26. use App\Form\Type\MapCoordinateType;
  27. use App\Form\Type\MessengersType;
  28. use App\Form\Type\PersonParametersType;
  29. use App\Form\Type\PhoneCallRestrictionsType;
  30. use App\Form\Type\ProvideServicesCollectionType;
  31. use App\Form\Type\TakeOutPricingType;
  32. use App\Repository\ApprovalRequestsRepository;
  33. use App\Repository\CityRepository;
  34. use App\Repository\ServiceRepository;
  35. use App\Service\DefaultCityProvider;
  36. use App\Service\Features;
  37. use App\Service\ImageProcessor;
  38. use App\Service\PhoneNumberService;
  39. use App\Service\EntitySortService;
  40. use App\Service\ProfileAdBoard;
  41. use App\Service\TextUniqueService;
  42. use App\Validator\Constraints\TextIsNotASpam;
  43. use Carbon\CarbonImmutable;
  44. use Cocur\Slugify\SlugifyInterface;
  45. use Doctrine\Common\Collections\ArrayCollection;
  46. use Doctrine\ORM\EntityManagerInterface;
  47. use Doctrine\ORM\EntityRepository;
  48. use League\Flysystem\FilesystemOperator;
  49. use Symfony\Bridge\Doctrine\Form\Type\EntityType;
  50. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  51. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  52. use Symfony\Component\Form\AbstractType;
  53. use Symfony\Component\Form\DataMapperInterface;
  54. use Symfony\Component\Form\Exception;
  55. use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
  56. use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  57. use Symfony\Component\Form\Extension\Core\Type\CollectionType;
  58. use Symfony\Component\Form\Extension\Core\Type\FileType;
  59. use Symfony\Component\Form\Extension\Core\Type\HiddenType;
  60. use Symfony\Component\Form\Extension\Core\Type\NumberType;
  61. use Symfony\Component\Form\Extension\Core\Type\SubmitType;
  62. use Symfony\Component\Form\Extension\Core\Type\TextareaType;
  63. use Symfony\Component\Form\Extension\Core\Type\TextType;
  64. use Symfony\Component\Form\FormBuilderInterface;
  65. use Symfony\Component\Form\FormError;
  66. use Symfony\Component\Form\FormEvent;
  67. use Symfony\Component\Form\FormEvents;
  68. use Symfony\Component\Form\FormInterface;
  69. use Symfony\Component\Form\FormView;
  70. use Symfony\Component\Form\Util\OrderedHashMapIterator;
  71. use Symfony\Component\OptionsResolver\OptionsResolver;
  72. use Symfony\Component\Validator\Constraints\Callback;
  73. use Symfony\Component\Validator\Constraints\GreaterThan;
  74. use Symfony\Component\Validator\Constraints\Length;
  75. use Symfony\Component\Validator\Constraints\NotBlank;
  76. use Symfony\Component\Validator\Constraints\NotNull;
  77. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  78. class CreateOrEditProfileForm extends AbstractType implements DataMapperInterface
  79. {
  80.     use ProvidedServicesFormTrait;
  81.     /** @var Service[] */
  82.     private array $services;
  83.     private mixed $needToCheckDescriptionIsUnique;
  84.     private mixed $originalProfileId null;
  85.     private ?TranslatableValue $originalProfileDescription null;
  86.     private bool $isFake false;
  87.     private ?Profile $profileBeforeFormDataApplied;
  88.     public function __construct(
  89.         private Features                   $features,
  90.         private DefaultCityProvider        $defaultCityProvider,
  91.         private CityRepository             $cityRepository,
  92.         private PhoneNumberService         $phoneNumberService,
  93.         private ApprovalRequestsRepository $approvalRequestsRepository,
  94.         private EntitySortService          $sortService,
  95.         private ServiceRepository          $serviceRepository,
  96.         private EntityManagerInterface     $entityManager,
  97.         private TextUniqueService          $textUniqueService,
  98.         private EventDispatcherInterface   $eventDispatcher,
  99.         private ProfileAdBoard             $profileAdBoard,
  100.         private SlugifyInterface           $slugify,
  101.         private FilesystemOperator         $profileMediaFilesystem,
  102.         private FilesystemOperator         $profileMediaSelfieFilesystem,
  103.         private ImageProcessor             $imageProcessor,
  104.         private ParameterBagInterface      $parameterBag,
  105.     )
  106.     {
  107.         $this->services $serviceRepository->findAll();
  108.     }
  109.     public function setIsFake(bool $fake): void
  110.     {
  111.         $this->isFake $fake;
  112.     }
  113.     /**
  114.      * @inheritDoc
  115.      */
  116.     public function buildForm(FormBuilderInterface $builder, array $options): void
  117.     {
  118.         $this->isFake $options['is_fake'] ?? false;
  119.         /** @var City $defaultCity */
  120.         $defaultCity $options['default_city'];
  121.         $this->needToCheckDescriptionIsUnique $options['check_description_is_unique'] ?? false;
  122.         if (is_array($options['original_profile_data'])) {
  123.             $this->originalProfileId $options['original_profile_data']['id'] ?? null;
  124.             $this->originalProfileDescription $options['original_profile_data']['description'] ?? null;
  125.         }
  126.         if ($this->features->multiple_cities()) {
  127.             $countryCode $defaultCity->getCountryCode();
  128.             $builder->add('city'EntityType::class, [
  129.                 'class' => City::class,
  130.                 'choice_label' => 'name',
  131.                 'choice_attr' => function (City $city): array {
  132.                     $mapCoordinate $city->getMapCoordinate();
  133.                     if (!$mapCoordinate) {
  134.                         return [];
  135.                     }
  136.                     return [
  137.                         'data-latitude' => $mapCoordinate->getLatitude(),
  138.                         'data-longitude' => $mapCoordinate->getLongitude(),
  139.                     ];
  140.                 },
  141.                 'query_builder' => function (EntityRepository $repository) use ($countryCode) {
  142.                     return $repository->createQueryBuilder('city')
  143.                         ->andWhere('city.countryCode = :countryCode')
  144.                         ->setParameter('countryCode'$countryCode);
  145.                 },
  146.             ]);
  147.             $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($defaultCity): void {
  148.                 $form $event->getForm();
  149.                 /** @var Profile $profile */
  150.                 $profile $event->getData();
  151.                 if (!$profile || $profile->isDraft()) {
  152.                     $form->get('city')->setData($defaultCity);
  153.                 }
  154.             });
  155.         }
  156.         $builder->add('map_coordinate'MapCoordinateType::class, [
  157.             'required' => false,
  158.         ]);
  159.         $builder->add('stations'EntityType::class, [
  160.             'required' => false,
  161.             'multiple' => true,
  162.             'class' => Station::class,
  163.             'choice_label' => 'name',
  164.             'choice_attr' => function (Station $station): array {
  165.                 return [
  166.                     'data-city' => $station->getCity()->getId(),
  167.                 ];
  168.             },
  169.             'query_builder' => function (EntityRepository $repository) {
  170.                 $qb $repository->createQueryBuilder('station');
  171.                 $this->sortService->modifyQueryBuilderToSortByCurrentTranslation($qb'station''name');
  172.                 return $qb;
  173.             },
  174.             'constraints' => [
  175.                 new Callback(function ($objectExecutionContextInterface $context$payload): void {
  176.                     if (!empty($object) && count($object) > 3) {
  177.                         $context->buildViolation('Max 3 stations can be chosen')
  178.                             ->atPath('stations')
  179.                             ->addViolation();
  180.                     }
  181.                 }),
  182.             ],
  183.         ]);
  184.         $builder->add('primary_station'HiddenType::class, [
  185.             'mapped' => false,
  186.             'required' => false,
  187.         ]);
  188.         $builder->add('provided_services'ProvideServicesCollectionType::class, [
  189.             'attr' => [
  190.                 'masseur_exclude_service_groups' => implode(',', [
  191.                     ServiceGroups::SEXServiceGroups::EXTREMEServiceGroups::BDSMServiceGroups::MISC,
  192.                 ]),
  193.             ],
  194.         ]);
  195.         if ($this->features->extended_profile_form()) {
  196.             $builder->add('client_types'ChoiceType::class, [
  197.                 'required' => false,
  198.                 'multiple' => true,
  199.                 'expanded' => true,
  200.                 'choices' => ClientTypes::getList(),
  201.             ]);
  202.         }
  203.         $builder->add('name'TextType::class, [
  204.             'constraints' => [
  205.                 new NotNull(),
  206.                 new NotBlank(),
  207.             ],
  208.         ]);
  209.         if ($this->features->has_profile_translations() && $this->features->has_translations()) {
  210.             $builder->add('name_en'TextType::class, [
  211.                 'constraints' => [
  212.                     new NotNull(),
  213.                     new NotBlank(),
  214.                 ],
  215.             ]);
  216.         }
  217.         $aboutConstraints = [
  218.             new NotNull(),
  219.             new NotBlank(),
  220.             new Length(['min' => 100]),
  221.         ];
  222.         if ($this->features->check_description_unique()) {
  223.             $aboutConstraints[] = new TextIsNotASpam();
  224.         }
  225.         $builder->add('about'TextareaType::class, [
  226.             'constraints' => $aboutConstraints,
  227.         ]);
  228.         if ($this->features->has_profile_translations() && $this->features->has_translations()) {
  229.             $builder->add('about_en'TextareaType::class, [
  230.                 'constraints' => $aboutConstraints,
  231.             ]);
  232.         }
  233.         if ($this->features->has_masseurs()) {
  234.             $builder->add('is_masseur'CheckboxType::class, [
  235.                 'required' => false,
  236.             ]);
  237.         }
  238.         $builder->add('person_parameters'PersonParametersType::class);
  239.         $builder->add('phone_number'TextType::class, [
  240.             'constraints' => [
  241.                 new NotNull(),
  242.                 new NotBlank(),
  243.             ],
  244.         ]);
  245.         if ($this->features->extended_profile_form()) {
  246.             $builder->add('messengers'MessengersType::class);
  247.         }
  248.         $builder->add('phone_call_restrictions'PhoneCallRestrictionsType::class);
  249.         $builder->add('client_restrictions'ClientRestrictionsType::class);
  250.         if ($this->features->crop_avatar()) {
  251.             $builder->add('avatar'HiddenType::class, [
  252.                 'required' => true,
  253.                 'constraints' => [
  254.                     new NotNull(),
  255.                     new NotBlank(),
  256.                 ],
  257.             ]);
  258.         }
  259.         $photosConstraints $this->features->crop_avatar() ? [] : [
  260.             new Callback(function ($objectExecutionContextInterface $context$payload): void {
  261.                 if (empty($object)) {
  262.                     $context->buildViolation('At least 1 photo should be uploaded')
  263.                         ->atPath('photos')
  264.                         ->addViolation();
  265.                 }
  266.             }),
  267.         ];
  268.         $builder->add('photos'CollectionType::class, [
  269.             'entry_type' => HiddenType::class,
  270.             'allow_add' => true,
  271.             'allow_delete' => true,
  272.             'delete_empty' => true,
  273.             'constraints' => $photosConstraints,
  274.         ]);
  275.         if (false == $this->features->crop_avatar()) {
  276.             $builder->add('main_photo'HiddenType::class, [
  277.                 'required' => true,
  278.                 'constraints' => [
  279.                     new NotNull(),
  280.                     new NotBlank(),
  281.                 ],
  282.             ]);
  283.         }
  284.         if ($this->features->extended_profile_form()) {
  285.             $builder->add('selfies'CollectionType::class, [
  286.                 'entry_type' => HiddenType::class,
  287.                 'allow_add' => true,
  288.                 'allow_delete' => true,
  289.                 'delete_empty' => true,
  290.             ]);
  291.         }
  292.         $builder->add('videos'CollectionType::class, [
  293.             'entry_type' => HiddenType::class,
  294.             'allow_add' => true,
  295.             'allow_delete' => true,
  296.             'delete_empty' => true,
  297.         ]);
  298.         $builder->add('media_params'HiddenType::class, [
  299.             'required' => false,
  300.         ]);
  301.         $builder->add('apartments_pricing'ApartmentsPricingType::class, [
  302.             'required' => false,
  303.         ]);
  304.         $builder->add('take_out_pricing'TakeOutPricingType::class, [
  305.             'required' => false,
  306.         ]);
  307.         if (false == $this->features->extended_profile_form()) {
  308.             $builder->add('extra_charge'NumberType::class, [
  309.                 'required' => false,
  310.                 'constraints' => new GreaterThan(0),
  311.             ]);
  312.         }
  313.         if ($this->features->extended_profile_form()) {
  314.             $builder->add('express_pricing'ExpressPricingType::class, [
  315.                 'required' => false,
  316.             ]);
  317.             $builder->add('car_pricing'CarPricingType::class, [
  318.                 'required' => false,
  319.             ]);
  320.         }
  321.         //TODO feature dependent!!!!!
  322.         //approval photo - see ProfileEditingController
  323.         if (false === 1) {
  324.             $builder->add('approval_photo'FileType::class, [
  325.                 'label' => 'Upload your photo for profile approval',
  326.                 'required' => false,
  327.             ]);
  328.         } else {
  329.             $builder->add('approval_photo'CollectionType::class, [
  330.                 'entry_type' => HiddenType::class,
  331.                 'allow_add' => true,
  332.                 'allow_delete' => true,
  333.                 'delete_empty' => true,
  334.                 'label' => 'Upload your photo for profile approval',
  335.                 'required' => false,
  336.             ]);
  337.         }
  338.         $builder->add('approval_status'HiddenType::class)->get('approval_status')->setData(null);
  339.         $builder->add('uploaded_approval_photo'HiddenType::class);
  340.         //end approval
  341.         $builder->add('submit'SubmitType::class);
  342.         $builder->addEventListener(FormEvents::POST_SUBMIT, [$this'onPostSubmit']);
  343.         $builder->setDataMapper($this);
  344.     }
  345.     /**
  346.      * @inheritDoc
  347.      */
  348.     public function buildView(FormView $viewFormInterface $form, array $options): void
  349.     {
  350.         if (null === $form->getData() && $form->has('is_masseur')) {
  351.             // Отметка чекбокса массажистки в форме создания анкеты
  352.             $form->get('is_masseur')->setData($options['precheck_masseur']);
  353.         }
  354.     }
  355.     /**
  356.      * @inheritDoc
  357.      */
  358.     public function configureOptions(OptionsResolver $resolver): void
  359.     {
  360.         $resolver->setRequired('default_city');
  361.         $resolver->setAllowedTypes('default_city'City::class);
  362.         $resolver->setDefined('precheck_masseur');
  363.         $resolver->setAllowedTypes('precheck_masseur''bool');
  364.         $resolver->setDefined('check_description_is_unique');
  365.         $resolver->setAllowedTypes('check_description_is_unique''bool');
  366.         $resolver->setDefined('original_profile_data');
  367.         $resolver->setDefined('is_fake');
  368.         $resolver->setAllowedTypes('is_fake''bool');
  369.         $resolver->setDefaults([
  370.             'data_class' => Profile::class,
  371.             'empty_data' => null,
  372.             'precheck_masseur' => false,
  373.             'allow_extra_fields' => true,
  374.             'check_description_is_unique' => true,
  375.             'original_profile_data' => null,
  376.             'is_fake' => false,
  377.         ]);
  378.     }
  379.     /**
  380.      * @inheritDoc
  381.      */
  382.     public function mapDataToForms($profile$forms)
  383.     {
  384.         /** @var FormInterface[] $forms */
  385.         $forms iterator_to_array($forms);
  386.         /** @var Profile $profile */
  387.         $forms['provided_services']->setData(
  388.             $this->serviceToProvideOptionsList(
  389.                 $this->serviceRepository,
  390.                 null !== $profile $profile->getProvidedServices()->toArray() : null
  391.             )
  392.         );
  393.         if (null === $profile) {
  394.             return null;
  395.         }
  396.         if (!$profile instanceof Profile) {
  397.             throw new Exception\UnexpectedTypeException($profileProfile::class);
  398.         }
  399.         if (isset($forms['city'])) {
  400.             $forms['city']->setData($profile->getCity());
  401.         }
  402.         $forms['map_coordinate']->setData($profile->getMapCoordinate());
  403.         $forms['stations']->setData($profile->getStations());
  404.         $forms['primary_station']->setData(
  405.             $profile->getPrimaryStation()?->getId()
  406.         );
  407.         if ($this->features->extended_profile_form()) {
  408.             $forms['client_types']->setData($profile->getClientTypes());
  409.         }
  410.         $forms['name']->setData($profile->getName()->getTranslation('ru'));
  411.         $forms['about']->setData($profile->getDescription()->getTranslation('ru'));
  412.         if (isset($forms['is_masseur'])) {
  413.             $forms['is_masseur']->setData($profile->isMasseur());
  414.         }
  415.         $forms['person_parameters']->setData($profile->getPersonParameters());
  416. //        $phoneNumberInternational = $this->phoneNumberService->getInternationalNumber($profile->getPhoneNumber(), $profile->getCity());
  417.         $forms['phone_number']->setData($profile->getPhoneNumber());
  418.         if ($this->features->extended_profile_form()) {
  419.             $forms['messengers']->setData($profile->getMessengers());
  420.         }
  421.         $forms['phone_call_restrictions']->setData($profile->getPhoneCallRestrictions());
  422.         $forms['client_restrictions']->setData($profile->getClientRestrictions());
  423.         $forms['apartments_pricing']->setData($profile->getApartmentsPricing());
  424.         $forms['take_out_pricing']->setData($profile->getTakeOutPricing());
  425.         if (false == $this->features->extended_profile_form()) {
  426.             $forms['extra_charge']->setData($profile->getExtraCharge());
  427.         }
  428.         if ($this->features->extended_profile_form()) {
  429.             $forms['car_pricing']->setData($profile->getCarPricing());
  430.             $forms['express_pricing']->setData($profile->getExpressPricing());
  431.         }
  432.         if ($this->features->crop_avatar()) {
  433.             if ($profile->getAvatar()) {
  434.                 $forms['avatar']->setData($profile->getAvatar()->getPath());
  435.             }
  436.         }
  437.         $videoPaths array_map(function (Video $video): string {
  438.             return $video->getPath();
  439.         }, $profile->getVideos()->toArray());
  440.         $forms['videos']->setData($videoPaths);
  441.         $photoPaths array_map(function (Photo $photo): string {
  442.             return $photo->getPath();
  443.         }, $profile->getPhotos()->toArray());
  444.         $forms['photos']->setData($photoPaths);
  445.         if (false == $this->features->crop_avatar()) {
  446.             if (null !== $mainPhoto $profile->getMainPhoto()) {
  447.                 $forms['main_photo']->setData($mainPhoto->getPath());
  448.             }
  449.         }
  450.         if ($this->features->extended_profile_form()) {
  451.             $photoPaths array_map(function (Photo $photo): string {
  452.                 return $photo->getPath();
  453.             }, $profile->getSelfies()->toArray());
  454.             $forms['selfies']->setData($photoPaths);
  455.         }
  456.         $approvalRequest $this->approvalRequestsRepository->getByProfile($profile);
  457.         $approvalMedia $approvalRequest ? ($this->features->approval_by_video() ? $approvalRequest->getVideo() : $approvalRequest->getPhoto()) : null;
  458.         if ($approvalRequest && $approvalRequest->isApproved() == false && $approvalMedia) {
  459.             $forms['uploaded_approval_photo']->setData($approvalMedia->getPath());
  460.         }
  461.         $forms['approval_status']->setData(
  462.             $approvalRequest && ($approvalRequest->isWaiting() || $approvalRequest->isRejected())
  463.                 ? $approvalRequest->getStatus()
  464.                 : ($profile->isApproved() ? ApprovalRequest::STATUS_APPROVED null)
  465.         );
  466.         if ($this->features->has_profile_translations() && $this->features->has_translations()) {
  467.             $forms['name_en']->setData($profile->getName()->getTranslation('en'));
  468.             $forms['about_en']->setData($profile->getDescription()->getTranslation('en'));
  469.         }
  470.     }
  471.     /**
  472.      * @inheritDoc
  473.      */
  474.     public function mapFormsToData($forms, &$profile): void
  475.     {
  476.         /** @var FormInterface[] $forms */
  477.         $forms iterator_to_array($forms);
  478.         if (!$profile instanceof Profile) {
  479.             $profile Profile::draft(CarbonImmutable::now());
  480.         }
  481.         $this->profileBeforeFormDataApplied = clone $profile;
  482.         //dump($profile->getAdBoardPlacement(), isset($data['is_masseur']), $profile->isMasseur());die;
  483.         if (
  484.             $profile->getAdBoardPlacement()
  485.             && (
  486.                 (!isset($forms['is_masseur']) && $profile->isMasseur())
  487.                 || (isset($forms['is_masseur']) && $profile->isMasseur() != (bool)$forms['is_masseur']->getData())
  488.             )
  489.         ) {
  490.             $this->profileAdBoard->doRemoveFromAdBoard($profile);
  491.         }
  492.         if (isset($forms['is_masseur'])) {
  493.             $isMasseur = (bool)$forms['is_masseur']->getData();
  494.             $profile->toggleMasseur($isMasseur);
  495.         } else {
  496.             $profile->toggleMasseur(false);
  497.         }
  498.         $name = (new TranslatableValue())->ru($forms['name']->getData());
  499.         $description = (new TranslatableValue())->ru($forms['about']->getData() ?? '');
  500.         if ($this->features->has_profile_translations() && $this->features->has_translations()) {
  501.             $name $name->en($forms['name_en']->getData());
  502.             $description $description->en($forms['about_en']->getData() ?? '');
  503.         }
  504.         if (!$this->slugify->slugify($name)) {
  505.             $forms['name']->addError(new FormError('Введите имя.'));
  506.         }
  507.         $profile->setBio($name$description);
  508.         if (isset($forms['city'])) {
  509.             $city $forms['city']->getData();
  510.         } else {
  511.             $city $this->defaultCityProvider->getDefaultCity();
  512.         }
  513.         /** @var ArrayCollection $stations */
  514.         $stations $forms['stations']->getData();
  515.         $stations $stations->filter(function ($station) use ($city): bool {
  516.             return $station->getCity()->getId() == $city->getId();
  517.         });
  518.         $profile->setLocation(
  519.             $city,
  520.             $stations,
  521.             $forms['map_coordinate']->getData()
  522.         );
  523.         $primaryStationId $forms['primary_station']->getData();
  524.         if ($primaryStationId) {
  525.             foreach ($stations as $station) {
  526.                 if ((string)$station->getId() === (string)$primaryStationId) {
  527.                     $profile->setPrimaryStation($station);
  528.                     break;
  529.                 }
  530.             }
  531.         }
  532.         /** @var PersonParameters $personParametersFormData */
  533.         $personParametersFormData $forms['person_parameters']->getData();
  534.         if (false == $profile->isDraft() && isset($forms['person_parameters']['gender'])) {
  535.             //при редактировании хоязину нельзя менять пол анкеты
  536.             if ($personParametersFormData->getGender() != $profile->getPersonParameters()->getGender()) {
  537.                 $personParametersFormData->setGender($profile->getPersonParameters()->getGender());
  538.             }
  539.         }
  540.         $profile->setPersonParameters($personParametersFormData);
  541.         if ($city != $this->defaultCityProvider->getDefaultCity()) {
  542.             $profile->getPersonParameters()->setGender(Genders::FEMALE);
  543.         }
  544.         if ($profile->getPersonParameters()->getGender() != Genders::FEMALE) {
  545.             $profile->toggleMasseur(false);
  546.         }
  547.         $this->serviceToProvideOptionsToProvidedServices($profile$forms['provided_services']->getData(), ProfileService::class, $this->serviceRepository);
  548.         if ($this->features->extended_profile_form()) {
  549.             $profile->setClientTypes($forms['client_types']->getData() ?? []);
  550.         }
  551.         $phoneNumber $forms['phone_number']->getData();
  552.         $phoneNumber $this->phoneNumberService->getInternationalNumber($phoneNumber$city);
  553.         if (null !== $phoneNumber) {
  554.             $profile->setPhoneCallOptions(
  555.                 $phoneNumber,
  556.                 $forms['phone_call_restrictions']->getData(),
  557.                 $this->features->extended_profile_form() ? $forms['messengers']->getData() : null
  558.             );
  559.         }
  560.         $profile->setClientRestrictions($forms['client_restrictions']->getData());
  561.         $profile->setPricing(
  562.             $forms['apartments_pricing']->getData(),
  563.             $forms['take_out_pricing']->getData(),
  564.             false == $this->features->extended_profile_form() ? $forms['extra_charge']->getData() : null,
  565.             $this->features->extended_profile_form() ? $forms['express_pricing']->getData() : null,
  566.             $this->features->extended_profile_form() ? $forms['car_pricing']->getData() : null
  567.         );
  568.         if ($this->features->crop_avatar()) {
  569.             if ($avatarPath $forms['avatar']->getData()) {
  570.                 if (!$profile->getAvatar() || $avatarPath != $profile->getAvatar()->getPath()) {
  571.                     if ($profile->getAvatar())
  572.                         $this->entityManager->remove($profile->getAvatar());
  573.                     $profile->setAvatar($avatarPath);
  574.                 }
  575.             }
  576.         }
  577.         $videoPaths $forms['videos']->getData();
  578.         if (is_array($videoPaths)) {
  579.             $existingVideoPaths $profile->getVideos()->map(function (Video $video): string {
  580.                 return $video->getPath();
  581.             })->getValues();
  582.             $videoPathsToAdd array_diff($videoPaths$existingVideoPaths);
  583.             $videoPathsToRemove array_diff($existingVideoPaths$videoPaths);
  584.             // Новые загруженные видео добавляются в отдельную очередь для последующей обработки
  585.             foreach ($videoPathsToAdd as $videoPath) {
  586.                 $profile->addRawVideo($videoPath);
  587.             }
  588.             foreach ($videoPathsToRemove as $videoPath) {
  589.                 $profile->removeVideo($videoPath);
  590.             }
  591.         }
  592.         $photoPaths $forms['photos']->getData();
  593.         if (is_array($photoPaths)) {
  594.             $existingPhotoPaths $profile->getPhotos()->map(function (Photo $photo): string {
  595.                 return $photo->getPath();
  596.             })->getValues();
  597.             $photoPathsToAdd array_diff($photoPaths$existingPhotoPaths);
  598.             $photoPathsToRemove array_diff($existingPhotoPaths$photoPaths);
  599.             foreach ($photoPathsToAdd as $photoPath)
  600.                 $profile->addPhoto($photoPathfalse);
  601.             foreach ($photoPathsToRemove as $photoPath)
  602.                 $profile->removePhoto($photoPath);
  603.             $photoPathsActual array_diff($photoPaths$photoPathsToRemove);
  604.             if (false === $this->isFake) {
  605.                 //апдейтим позиции
  606.                 ksort($photoPathsActual);
  607.                 $photoPathsActual array_values($photoPathsActual);
  608.                 $i 0;
  609.                 foreach ($profile->getPhotos() as $k => $photo) {
  610.                     if ($photoPathsActual[$i] != $photo->getPath()) {
  611.                         $photo->setPath($photoPathsActual[$i]);
  612.                     }
  613.                     $i++;
  614.                 }
  615.             }
  616.         }
  617.         if (false == $this->features->crop_avatar()) {
  618.             if (null !== $mainPhoto $forms['main_photo']->getData()) {
  619.                 $profile->changeMainPhoto($mainPhoto);
  620.             }
  621.         }
  622.         if ($this->features->extended_profile_form()) {
  623.             $selfiePaths $forms['selfies']->getData();
  624.             if (is_array($selfiePaths)) {
  625.                 $existingPhotoPaths $profile->getSelfies()->map(function (Photo $photo): string {
  626.                     return $photo->getPath();
  627.                 })->getValues();
  628.                 $photoPathsToAdd array_diff($selfiePaths$existingPhotoPaths);
  629.                 $photoPathsToRemove array_diff($existingPhotoPaths$selfiePaths);
  630.                 foreach ($photoPathsToAdd as $photoPath) {
  631.                     $profile->addSelfie($photoPathfalse);
  632.                 }
  633.                 foreach ($photoPathsToRemove as $photoPath) {
  634.                     if ($profile->removeSelfie($photoPath)) {
  635.                         $this->profileMediaSelfieFilesystem->delete($photoPath);
  636.                     }
  637.                 }
  638.                 $photoPathsActual array_diff($selfiePaths$photoPathsToRemove);
  639.                 if (false === $this->isFake) {
  640.                     //апдейтим позиции
  641.                     ksort($photoPathsActual);
  642.                     $photoPathsActual array_values($photoPathsActual);
  643.                     $i 0;
  644.                     foreach ($profile->getSelfies() as $k => $photo) {
  645.                         if ($photoPathsActual[$i] != $photo->getPath()) {
  646.                             $photo->setPath($photoPathsActual[$i]);
  647.                         }
  648.                         $i++;
  649.                     }
  650.                 }
  651.             }
  652.         }
  653.         if (false === $this->isFake) {
  654.             $mediaParams $forms['media_params']->getData() ?? '[]';
  655.             $mediaParams json_decode($mediaParamstrue);
  656.             foreach ($mediaParams as $type => $media) {
  657.                 foreach ($media as $path => $params) {
  658.                     $this->imageProcessor->rotateImage($path, -$params['rotation'], $this->profileMediaFilesystem);
  659.                     $this->eventDispatcher->dispatch(new UploadedFileModified($path$this->parameterBag->get('app.path.media.profile')), UploadedFileModified::NAME);
  660.                 }
  661.             }
  662.         }
  663.         if (true == $profile->isDraft()) {
  664.             $randomPhoneNumber $this->phoneNumberService->generateRandomPhone();
  665.             $profile->setSeoPhoneNumber($randomPhoneNumber);
  666.         }
  667.     }
  668.     /**
  669.      * @inheritDoc
  670.      */
  671.     public function getBlockPrefix()
  672.     {
  673.         return '';
  674.     }
  675.     public function onPostSubmit(FormEvent $event): void
  676.     {
  677.         $form $event->getForm();
  678.         $profile $this->profileBeforeFormDataApplied;
  679.         if ($this->features->check_description_unique() && $this->needToCheckDescriptionIsUnique) {
  680.             if (null == $profile->getDescription()
  681.                 || ($this->originalProfileDescription && $profile->getDescription()->getTranslation('ru') != $this->originalProfileDescription->getTranslation('ru'))) {
  682.                 if (($about $form->get('about'))->getErrors()->count() == 0) {
  683.                     $isUnique null !== $about && $this->textUniqueService->isProfileTextUnique($about->getData() ?? ''$this->originalProfileId ? [$this->originalProfileId] : []);
  684.                     if (false == $isUnique) {
  685.                         $form->addError(new FormError('Ваш текст не уникален. Допускаются анкеты только с уникальным описанием. Измените текст.'));
  686.                         $about->addError(new FormError('Ваш текст не уникален. Допускаются анкеты только с уникальным описанием. Измените текст.'));
  687.                     }
  688.                 }
  689.             }
  690.             if (null == $profile->getDescription()
  691.                 || ($this->originalProfileDescription && $profile->getDescription()->getTranslation('en') != $this->originalProfileDescription->getTranslation('en'))) {
  692.                 if (isset($forms['about_en']) && ($about_en $form->get('about_en'))->getErrors()->count() == 0) {
  693.                     $isUnique null !== $about_en && $this->textUniqueService->isProfileTextUnique($about_en->getData() ?? ''$this->originalProfileId ? [$this->originalProfileId] : []);
  694.                     if (false == $isUnique) {
  695.                         $form->addError(new FormError('Ваш текст не уникален. Допускаются анкеты только с уникальным описанием. Измените текст.'));
  696.                         $about_en->addError(new FormError('Ваш текст не уникален. Допускаются анкеты только с уникальным описанием. Измените текст.'));
  697.                     }
  698.                 }
  699.             }
  700.         }
  701.     }
  702.     protected function canApplyForApproval(Profile $profile, ?ApprovalRequest $approvalRequest): bool
  703.     {
  704.         return false == $profile->isApproved()
  705.             && (!$approvalRequest instanceof \App\Entity\Profile\Confirmation\ApprovalRequest || false == $approvalRequest->isWaiting());
  706.     }
  707. }
  708. //        $builder->add('services', EntityType::class, [
  709. //            'multiple' => true,
  710. //            'expanded' => true,
  711. //            'class' => Service::class,
  712. //            'choice_label' => 'name',
  713. //            'choice_attr' => function (Service $service) {
  714. //                return [
  715. //                    'service-group' => $service->getGroup(),
  716. //                ];
  717. //            },
  718. //            'query_builder' => function (EntityRepository $repository) {
  719. //                return $repository->createQueryBuilder('service')
  720. //                    ->addOrderBy('service.group')
  721. //                    ->addOrderBy('service.id')
  722. //                    ;
  723. //            },
  724. //            'mapped' => false, //???
  725. //        ]);