src/Entity/Profile/Profile.php line 74

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-03-19
  5.  * Time: 15:36
  6.  */
  7. namespace App\Entity\Profile;
  8. use AngelGamez\TranslatableBundle\Entity\TranslatableValue;
  9. //use ApiPlatform\Core\Annotation\ApiProperty;
  10. use App\Entity\Account\Advertiser;
  11. use App\Entity\ApartmentsPricing;
  12. use App\Entity\ContainsDomainEvents;
  13. use App\Entity\Account\Customer;
  14. use App\Entity\DomainEventsRecorderTrait;
  15. use App\Entity\ExpressPricing;
  16. use App\Entity\IProvidesServices;
  17. use App\Entity\Location\City;
  18. use App\Entity\Location\MapCoordinate;
  19. use App\Entity\Location\Station;
  20. use App\Entity\Messengers;
  21. use App\Entity\PhoneCallRestrictions;
  22. use App\Entity\Profile\Comment\CommentByCustomer;
  23. use App\Entity\Profile\Confirmation\ModerationRequest;
  24. use App\Entity\Sales\Profile\AdBoardPlacement;
  25. use App\Entity\Sales\Profile\PlacementHiding;
  26. use App\Entity\Sales\Profile\TopPlacement;
  27. use App\Entity\ProvidedServiceTrait;
  28. use App\Entity\TakeOutPricing;
  29. use App\Repository\ProfileRepository;
  30. use Carbon\Carbon;
  31. use Carbon\CarbonImmutable;
  32. use Doctrine\Common\Collections\ArrayCollection;
  33. use Doctrine\Common\Collections\Collection;
  34. use Doctrine\ORM\Mapping as ORM;
  35. use Doctrine\ORM\Mapping\Index;
  36. //use ApiPlatform\Core\Annotation\ApiResource;
  37. //use ApiPlatform\Core\Annotation\ApiFilter;
  38. //use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
  39. //use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter;
  40. use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
  41. use Symfony\Component\Serializer\Annotation\Groups;
  42. use Gedmo\Mapping\Annotation as Gedmo;
  43. use App\Validator\Constraints\ValidPhoneForCountry as ValidPhoneForCountryAssert;
  44. use App\Validator\Constraints\PhoneNotBlack as PhoneNotBlackAssert;
  45. /**
  46.  * ApiResource(collectionOperations={"get"}, itemOperations={"get"}, normalizationContext={"groups"={"profile"}}, attributes={"pagination_client_enabled"=true, "pagination_client_items_per_page"=true})
  47.  * ApiFilter(SearchFilter::class, properties={"city": "exact", "providedServices": "exact"})
  48.  * ApiFilter(RangeFilter::class, properties={"personParameters.age", "personParameters.height", "personParameters.weight", "personParameters.breastSize", "apartmentsPricing.oneHourPrice"})
  49.  * @ValidPhoneForCountryAssert/ProtocolClass
  50.  * @PhoneNotBlackAssert/ProtocolClass
  51.  */
  52. #[Gedmo\SoftDeleteable(fieldName"deletedAt"timeAwaretrue)]
  53. #[ORM\Table(name'profiles')]
  54. #[Index(name'idx_deleted_at'columns: ['deleted_at'])]
  55. #[Index(name'idx_created_at'columns: ['created_at'])]
  56. #[Index(name'idx_gender'columns: ['person_gender'])]
  57. #[Index(name'idx_apartments_one_hour_price'columns: ['apartments_one_hour_price'])]
  58. #[Index(name'idx_is_dummy'columns: ['is_dummy'])]
  59. #[Index(name'idx_city_deleted'columns: ['city_id''deleted_at'])]
  60. #[Index(name'idx_city_deleted_moderation'columns: ['city_id''deleted_at''moderation_status'])]
  61. #[Index(name'idx_city_masseur_deleted'columns: ['city_id''is_masseur''deleted_at'])]
  62. #[Index(name'idx_city_masseur_deleted_moderation'columns: ['city_id''is_masseur''deleted_at''moderation_status'])]
  63. #[Index(name'idx_city_deleted_gender'columns: ['city_id''deleted_at''person_gender'])]
  64. #[Index(name'idx_city_deleted_moderation_gender'columns: ['city_id''deleted_at''moderation_status''person_gender'])]
  65. #[Index(name'idx_city_masseur_deleted_gender'columns: ['city_id''is_masseur''deleted_at''person_gender'])]
  66. #[Index(name'idx_city_masseur_deleted_moderation_gender'columns: ['city_id''is_masseur''deleted_at''moderation_status''person_gender'])]
  67. #[ORM\Entity(repositoryClassProfileRepository::class)]
  68. #[ORM\HasLifecycleCallbacks]
  69. class Profile implements ContainsDomainEventsIProvidesServices
  70. {
  71.     use SoftDeleteableEntity;
  72.     use DomainEventsRecorderTrait;
  73.     use ProvidedServiceTrait;
  74.     const MODERATION_STATUS_NOT_PASSED 0;
  75.     const MODERATION_STATUS_APPROVED 1;
  76.     const MODERATION_STATUS_WAITING 2;
  77.     const MODERATION_STATUS_REJECTED 3;
  78.     #[ORM\Id]
  79.     #[ORM\Column(name'id'type'integer')]
  80.     #[ORM\GeneratedValue(strategy'AUTO')]
  81.     #[Groups('profile')]
  82.     protected int $id;
  83.     #[ORM\JoinColumn(name'user_id'referencedColumnName'id'nullabletrue)]
  84.     #[ORM\ManyToOne(targetEntityAdvertiser::class, inversedBy'profiles')]
  85.     protected ?Advertiser $owner;
  86.     #[ORM\Column(name'is_dummy'type'boolean'options: ['default' => 0])]
  87.     protected bool $dummy false;
  88.     /** @var TopPlacement[] */
  89.     #[ORM\OneToMany(targetEntityTopPlacement::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  90.     protected Collection $topPlacements;
  91.     #[ORM\OneToOne(targetEntityAdBoardPlacement::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  92.     protected ?AdBoardPlacement $adBoardPlacement null;
  93.     #[ORM\OneToOne(targetEntityPlacementHiding::class, mappedBy'profile'cascade: ['all'])]
  94.     protected ?PlacementHiding $placementHiding null;
  95.     #[ORM\Column(name'uri_identity'type'string'length64)]
  96.     #[Groups('profile')]
  97.     protected string $uriIdentity;
  98.     #[ORM\Column(name'name'type'translatable')]
  99.     #[Groups('profile')]
  100.     protected TranslatableValue $name;
  101.     #[ORM\Column(name'description'type'translatable')]
  102.     #[Groups('profile')]
  103.     protected ?TranslatableValue $description null;
  104.     #[ORM\Embedded(class: PersonParameters::class, columnPrefix'person_')]
  105.     #[Groups('profile')]
  106.     protected PersonParameters $personParameters;
  107.     /** var ProfileService[] */
  108.     #[ORM\OneToMany(targetEntityProfileService::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  109.     #[ORM\Cache(usage'NONSTRICT_READ_WRITE'region'profiles')]
  110.     protected Collection $providedServices;
  111.     /** @var int[] */
  112.     #[ORM\Column(name'client_types'type'simple_array'nullabletrue)]
  113.     protected ?array $clientTypes;
  114.     #[ORM\Column(name'phone_number'type'string'length24)]
  115.     #[Groups('profile')]
  116.     protected string $phoneNumber;
  117.     #[ORM\Embedded(class: Messengers::class, columnPrefixfalse)]
  118.     #[Groups('profile')]
  119.     protected ?Messengers $messengers null;
  120.     #[ORM\Embedded(class: PhoneCallRestrictions::class, columnPrefixfalse)]
  121.     protected ?PhoneCallRestrictions $phoneCallRestrictions null;
  122.     #[ORM\Column(name'is_masseur'type'boolean')]
  123.     protected bool $masseur false;
  124.     #[ORM\Embedded(class: ClientRestrictions::class, columnPrefixfalse)]
  125.     protected ?ClientRestrictions $clientRestrictions null;
  126.     #[ORM\Embedded(class: ApartmentsPricing::class, columnPrefixfalse)]
  127.     #[Groups('profile')]
  128.     protected ?ApartmentsPricing $apartmentsPricing null;
  129.     #[ORM\Embedded(class: TakeOutPricing::class, columnPrefixfalse)]
  130.     #[Groups('profile')]
  131.     protected ?TakeOutPricing $takeOutPricing null;
  132.     #[ORM\Embedded(class: ExpressPricing::class, columnPrefixfalse)]
  133.     #[Groups('profile')]
  134.     protected ?ExpressPricing $expressPricing null;
  135.     #[ORM\Embedded(class: CarPricing::class, columnPrefixfalse)]
  136.     #[Groups('profile')]
  137.     protected ?CarPricing $carPricing null;
  138.     #[ORM\Column(name'extra_charge'type'integer'nullabletrue)]
  139.     #[Groups('profile')]
  140.     protected ?int $extraCharge;
  141.     #[ORM\Column(name'prepayment'type'boolean'nullabletrue)]
  142.     protected ?bool $prepayment null;
  143.     #[ORM\Column(name'prepayment_amount'type'integer'nullabletrue)]
  144.     protected ?int $prepaymentAmount null;
  145.     #[ORM\Column(name'prepayment_comment'type'string'length512nullabletrue)]
  146.     protected ?string $prepaymentComment null;
  147.     /** @var Photo[] */
  148.     #[ORM\OneToMany(targetEntityPhoto::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  149.     #[Groups('profile')]
  150.     protected Collection $photos;
  151.     /** @var Selfie[] */
  152.     #[ORM\OneToMany(targetEntitySelfie::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  153.     #[Groups('profile')]
  154.     protected Collection $selfies;
  155.     /** @var Video[] */
  156.     #[ORM\OneToMany(targetEntityVideo::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  157.     protected Collection $videos;
  158.     #[ORM\OneToMany(targetEntityFileProcessingTask::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  159.     protected Collection $processingFiles;
  160.     #[ORM\OneToOne(targetEntityAdminApprovalPhoto::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  161.     protected ?AdminApprovalPhoto $adminApprovalPhoto null;
  162.     #[ORM\OneToOne(targetEntityAvatar::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  163.     protected ?Avatar $avatar null;
  164.     /** @var CommentByCustomer[] */
  165.     #[ORM\OneToMany(targetEntityCommentByCustomer::class, mappedBy'profile')]
  166.     protected Collection $comments;
  167.     #[ORM\Column(name'is_approved'type'boolean')]
  168.     #[Groups('profile')]
  169.     protected bool $approved false;
  170.     #[ORM\Column(name'moderation_status'type'integer')]
  171.     #[Groups('profile')]
  172.     protected int $moderationStatus 0;
  173.     #[ORM\JoinColumn(name'city_id'referencedColumnName'id')]
  174.     #[ORM\ManyToOne(targetEntityCity::class)]
  175.     #[ORM\Cache(usage'NONSTRICT_READ_WRITE'region'profiles')]
  176.     protected City $city;
  177.     /** @var Station[] */
  178.     //, indexBy="id"
  179.     #[ORM\JoinTable(name'profile_stations')]
  180.     #[ORM\JoinColumn(name'profile_id'referencedColumnName'id')]
  181.     #[ORM\InverseJoinColumn(name'station_id'referencedColumnName'id')]
  182.     #[ORM\ManyToMany(targetEntityStation::class)]
  183.     #[Groups('profile')]
  184.     #[ORM\Cache(usage'NONSTRICT_READ_WRITE'region'profiles')]
  185.     protected Collection $stations;
  186.     #[ORM\Embedded(class: MapCoordinate::class, columnPrefixfalse)] // ApiProperty()
  187.     #[Groups('profile')]
  188.     protected ?MapCoordinate $mapCoordinate;
  189.     #[ORM\Column(name'created_at'type'datetimetz_immutable'nullabletrue)]
  190.     protected ?\DateTimeImmutable $createdAt;
  191.     #[Gedmo\Timestampable(on"change"field: ["name""description""personParameters""providedServices""clientTypes""phoneNumber""messengers""phoneCallrestrictions""masseur""clientRestrictions""apartmentsPricing""takeOutPricing""expressPricing""carPricing""extraCharge""prepayment""prepaymentAmount""prepaymentComment""photos""selfies""videos""avatar""stations""mapCoordinate"])]
  192.     #[ORM\Column(name'updated_at'type'datetimetz_immutable'nullabletrue)]
  193.     #[Groups('profile')]
  194.     protected ?\DateTimeImmutable $updatedAt;
  195.     #[ORM\Column(name'inactivated_at'type'datetimetz_immutable'nullabletrue)]
  196.     protected ?\DateTimeImmutable $inactivatedAt;
  197.     private bool $draft false;
  198.     #[ORM\Column(name'seo'type'json'nullabletrue)]
  199.     #[Groups('profile')]
  200.     private ?array $seo null;
  201.     #[ORM\ManyToOne(targetEntityStation::class)]
  202.     #[ORM\JoinColumn(name'primary_station_id'referencedColumnName'id'nullabletrueonDelete'SET NULL')]
  203.     #[Groups('profile')]
  204.     #[ORM\Cache(usage'READ_ONLY')]
  205.     private ?Station $primaryStation null;
  206.     #[ORM\Column(type'smallint'options: ['default' => 0])]
  207.     private int $deleteMode 0;
  208.     protected function __construct(?\DateTimeImmutable $createdAt)
  209.     {
  210.         $this->draft true;
  211.         $this->createdAt $createdAt;
  212.         $this->photos = new ArrayCollection();
  213.         $this->selfies = new ArrayCollection();
  214.         $this->videos = new ArrayCollection();
  215.         $this->processingFiles = new ArrayCollection();
  216.         $this->comments = new ArrayCollection();
  217.         $this->topPlacements = new ArrayCollection();
  218.         $this->providedServices = new ArrayCollection();
  219.         $this->stations = new ArrayCollection();
  220.         $this->inactivatedAt CarbonImmutable::now();
  221.     }
  222.     public static function draft(?\DateTimeImmutable $createdAt null, ?bool $dummy null): self
  223.     {
  224.         $profile = new static($createdAt);
  225.         if (null !== $dummy)
  226.             $profile->dummy $dummy;
  227.         return $profile;
  228.     }
  229.     public static function create(string $uriIdentity, ?\DateTimeImmutable $createdAt null): self
  230.     {
  231.         $profile = new static($createdAt);
  232.         $profile->defineUriIdentity($uriIdentity);
  233.         $profile->toggleMasseur(false);
  234.         return $profile;
  235.     }
  236.     public function defineUriIdentity(string $uriIdentity): void
  237.     {
  238.         if (!$this->isDraft()) {
  239.             throw new \DomainException('Profile is already created and can\'t change its URI.');
  240.         }
  241.         $this->uriIdentity $uriIdentity;
  242.         $this->draft false;
  243.     }
  244.     public function isDraft(): bool
  245.     {
  246.         return $this->draft;
  247.     }
  248.     public function toggleMasseur(bool $isMasseur): void
  249.     {
  250.         if ($this->masseur !== $isMasseur && (null !== $this->adBoardPlacement && false == $this->adBoardPlacement->getType()->isFree())) {
  251.             throw new \DomainException('Impossible to toggle profile type while it is displaying on adboard.');
  252.         }
  253.         $this->masseur $isMasseur;
  254.     }
  255.     public static function createMasseur(string $uriIdentity, ?\DateTimeImmutable $createdAt null): self
  256.     {
  257.         $profile = new static($createdAt);
  258.         $profile->defineUriIdentity($uriIdentity);
  259.         $profile->toggleMasseur(true);
  260.         return $profile;
  261.     }
  262.     public function isOwnedBy(Advertiser $account): bool
  263.     {
  264.         return $account->getId() === $this->owner->getId();
  265.     }
  266.     public function getId(): int
  267.     {
  268.         return $this->id;
  269.     }
  270.     public function setBio(TranslatableValue $nameTranslatableValue $description): void
  271.     {
  272.         $this->name $name;
  273.         $this->description $description;
  274.     }
  275.     public function setLocation(City $city$stations, ?MapCoordinate $mapCoordinate): void
  276.     {
  277.         if (!$this->isDraft() && !$this->city->equals($city)) {
  278.             throw new \DomainException('City change for a saved profile is forbidden.');
  279.         }
  280.         $this->city $city;
  281.         $this->changeStations($stations);
  282.         $this->normalizePrimaryStation();
  283.         $this->mapCoordinate $mapCoordinate;
  284.     }
  285.     protected function changeStations($stations)
  286.     {
  287.         if (null === $stations)
  288.             return;
  289.         if (false === is_array($stations) && false === is_iterable($stations))
  290.             throw new \InvalidArgumentException('Stations list should be either an array or an ArrayCollection');
  291.         $stationsArray is_iterable($stations) && !is_array($stations) ? iterator_to_array($stations) : $stations;
  292.         $stations = [];
  293.         foreach ($stationsArray as $station) {
  294.             $stations[$station->getId()] = $station;
  295.         }
  296.         $stationIds array_map(function (Station $station): int {
  297.             return $station->getId();
  298.         }, $stations);
  299.         $existingStationIds $this->stations->map(function (Station $station): int {
  300.             return $station->getId();
  301.         })->getValues();
  302.         $stationIdsToAdd array_diff($stationIds$existingStationIds);
  303.         $stationIdsToRemove array_diff($existingStationIds$stationIds);
  304.         foreach ($stationIdsToAdd as $stationId) {
  305.             $this->stations->add($stations[$stationId]);
  306.         }
  307.         foreach ($stationIdsToRemove as $stationId) {
  308.             $this->stations->remove($stationId);
  309.         }
  310.     }
  311.     public function normalizePrimaryStation(): void
  312.     {
  313.         if ($this->stations->isEmpty()) {
  314.             $this->primaryStation null;
  315.             return;
  316.         }
  317.         if ($this->primaryStation === null || !$this->stations->contains($this->primaryStation)) {
  318.             $this->primaryStation $this->stations->first();
  319.         }
  320.     }
  321.     public function setEnabledProvidedServices($services): void
  322.     {
  323.         if (null !== $services) {
  324.             if (is_array($services)) {
  325.                 $services = new ArrayCollection($services);
  326.             } elseif (!$services instanceof ArrayCollection) {
  327.                 if (is_iterable($services)) {
  328.                     $services = new ArrayCollection(iterator_to_array($services));
  329.                 } else {
  330.                     throw new \InvalidArgumentException('Services list should be either an array or an ArrayCollection');
  331.                 }
  332.             }
  333.             $this->providedServices $services;
  334.         }
  335.     }
  336.     public function setPhoneCallOptions(string $phoneNumber, ?PhoneCallRestrictions $restrictions, ?Messengers $messengers): void
  337.     {
  338.         $this->phoneNumber $phoneNumber;
  339.         $this->phoneCallRestrictions $restrictions;
  340.         $this->messengers $messengers;
  341.     }
  342.     public function setPricing(?ApartmentsPricing $apartmentsPricing, ?TakeOutPricing $takeOutPricing, ?int $extraCharge, ?ExpressPricing $expressPricing null, ?CarPricing $carPricing null): void
  343.     {
  344.         $this->apartmentsPricing $apartmentsPricing;
  345.         $this->takeOutPricing $takeOutPricing;
  346.         $this->extraCharge $extraCharge;
  347.         $this->expressPricing $expressPricing;
  348.         $this->carPricing $carPricing;
  349.     }
  350.     public function setPrepaymentOptions(?bool $prepayment, ?int $prepaymentAmount, ?string $prepaymentComment): void
  351.     {
  352.         $this->prepayment $prepayment;
  353.         if ($prepayment !== true) {
  354.             $this->prepaymentAmount null;
  355.             $this->prepaymentComment null;
  356.             return;
  357.         }
  358.         $this->prepaymentAmount $prepaymentAmount;
  359.         $this->prepaymentComment $prepaymentComment;
  360.     }
  361.     public function isApproved(): bool
  362.     {
  363.         return $this->approved;
  364.     }
  365.     public function approve(): void
  366.     {
  367.         $this->approved true;
  368.     }
  369.     public function unApprove(): void
  370.     {
  371.         $this->approved false;
  372.     }
  373.     public function getOwner(): ?Advertiser
  374.     {
  375.         return $this->owner;
  376.     }
  377.     public function setOwner(Advertiser $owner): void
  378.     {
  379.         $this->owner $owner;
  380.     }
  381.     public function hasOwner(): bool
  382.     {
  383.         return null !== $this->owner;
  384.     }
  385.     public function getTopPlacements(): Collection
  386.     {
  387.         return $this->topPlacements;
  388.     }
  389.     public function addTopPlacement(TopPlacement $topPlacement): void
  390.     {
  391.         $this->topPlacements->add($topPlacement);
  392.     }
  393.     public function getAdBoardPlacement(): ?AdBoardPlacement
  394.     {
  395.         return $this->adBoardPlacement;
  396.     }
  397.     public function setAdBoardPlacement(AdBoardPlacement $adBoardPlacement): void
  398.     {
  399.         $this->adBoardPlacement $adBoardPlacement;
  400.     }
  401.     /**
  402.      * Анкета оплачена и выводится в общих списках на сайте
  403.      * или в ТОПе, то есть "АКТИВНА"
  404.      */
  405.     public function isActive(): bool
  406.     {
  407.         return null !== $this->adBoardPlacement || $this->hasRunningTopPlacement();
  408.     }
  409.     public function hasRunningTopPlacement(): bool
  410.     {
  411.         $now = new \DateTimeImmutable('now');
  412.         foreach ($this->topPlacements as /** @var TopPlacement $topPlacement */ $topPlacement) {
  413.             if ($topPlacement->getPlacedAt() <= $now && $now <= $topPlacement->getExpiresAt())
  414.                 return true;
  415.         }
  416.         return false;
  417.     }
  418.     public function getUriIdentity(): string
  419.     {
  420.         return $this->uriIdentity;
  421.     }
  422.     public function getName(): TranslatableValue
  423.     {
  424.         return $this->name;
  425.     }
  426.     public function getDescription(): ?TranslatableValue
  427.     {
  428.         return $this->description;
  429.     }
  430.     public function getPersonParameters(): PersonParameters
  431.     {
  432.         return $this->personParameters;
  433.     }
  434.     public function setPersonParameters(PersonParameters $personParameters): void
  435.     {
  436.         $this->personParameters $personParameters;
  437.     }
  438.     public function getPhoneNumber(): string
  439.     {
  440.         return $this->phoneNumber;
  441.     }
  442.     //TODO return type
  443.     public function getPhoneCallRestrictions(): ?PhoneCallRestrictions
  444.     {
  445.         return $this->phoneCallRestrictions;
  446.     }
  447.     public function isMasseur(): bool
  448.     {
  449.         return $this->masseur;
  450.     }
  451.     //TODO return type
  452.     public function getClientRestrictions(): ?ClientRestrictions
  453.     {
  454.         return $this->clientRestrictions;
  455.     }
  456.     //TODO return type
  457.     public function setClientRestrictions(?ClientRestrictions $restrictions): void
  458.     {
  459.         $this->clientRestrictions $restrictions;
  460.     }
  461.     //TODO return type
  462.     public function getApartmentsPricing(): ?ApartmentsPricing
  463.     {
  464.         return $this->apartmentsPricing;
  465.     }
  466.     public function getTakeOutPricing(): ?TakeOutPricing
  467.     {
  468.         return $this->takeOutPricing;
  469.     }
  470.     public function getExtraCharge(): ?int
  471.     {
  472.         return $this->extraCharge;
  473.     }
  474.     public function isPrepayment(): ?bool
  475.     {
  476.         return $this->prepayment;
  477.     }
  478.     public function getPrepaymentAmount(): ?int
  479.     {
  480.         return $this->prepaymentAmount;
  481.     }
  482.     public function getPrepaymentComment(): ?string
  483.     {
  484.         return $this->prepaymentComment;
  485.     }
  486.     public function addPhoto(string $pathbool $isMain): Photo
  487.     {
  488.         $photos $this->getPhotos();
  489.         $found $photos->filter(function (Photo $photo) use ($path): bool {
  490.             return $path === $photo->getPath();
  491.         });
  492.         if (!$found->isEmpty())
  493.             return $found->first();
  494.         if (true === $isMain) {
  495.             $photos->forAll(function ($indexPhoto $photo): true {
  496.                 $photo->unsetMain();
  497.                 return true;
  498.             });
  499.         }
  500.         $photo = new Photo($this$path$isMain);
  501.         $this->photos->add($photo);
  502.         return $photo;
  503.     }
  504.     /**
  505.      * @return Photo[]
  506.      */
  507.     public function getPhotos(): Collection
  508.     {
  509.         return $this->photos->filter(function ($mediaFile): bool {
  510.             return get_class($mediaFile) == Photo::class;
  511.         });
  512.     }
  513.     public function removePhoto(string $path): bool
  514.     {
  515.         foreach ($this->getPhotos() as $photo) {
  516.             if ($path === $photo->getPath()) {
  517.                 $this->photos->removeElement($photo);
  518.                 return true;
  519.             }
  520.         }
  521.         return false;
  522.     }
  523.     public function getMainPhotoOrFirstPhoto(): ?Photo
  524.     {
  525.         $photos $this->getPhotos();
  526.         if ($photos->isEmpty()) {
  527.             return null;
  528.         }
  529.         $mainPhoto $this->getMainPhoto();
  530.         if (null === $mainPhoto) {
  531.             $mainPhoto $photos->first();
  532.         }
  533.         return $mainPhoto;
  534.     }
  535.     public function getMainPhoto(): ?Photo
  536.     {
  537.         $photos $this->getPhotos();
  538.         if ($photos->isEmpty()) {
  539.             return null;
  540.         }
  541.         $mainPhoto null;
  542.         $photos->forAll(function ($indexPhoto $photo) use (&$mainPhoto): bool {
  543.             if ($photo->isMain()) {
  544.                 $mainPhoto $photo;
  545.                 return false// Stop the cycle
  546.             }
  547.             return true;
  548.         });
  549.         return $mainPhoto;
  550.     }
  551.     public function changeMainPhoto(string $path): void
  552.     {
  553.         $photos $this->getPhotos();
  554.         $found $photos->filter(function (Photo $photo) use ($path): bool {
  555.             return $path === $photo->getPath();
  556.         });
  557.         if ($found->isEmpty()) {
  558.             return;
  559.         }
  560.         $mainPhoto $found->first();
  561.         $photos->forAll(function ($indexPhoto $photo): true {
  562.             $photo->unsetMain();
  563.             return true;
  564.         });
  565.         $mainPhoto->setMain();
  566.     }
  567.     public function addSelfie(string $path): Selfie
  568.     {
  569.         $found $this->getSelfies()->filter(function (Selfie $selfie) use ($path): bool {
  570.             return $path === $selfie->getPath();
  571.         });
  572.         if (!$found->isEmpty())
  573.             return $found->first();
  574.         $selfie = new Selfie($this$path);
  575.         $this->selfies->add($selfie);
  576.         return $selfie;
  577.     }
  578.     /**
  579.      * @return Selfie[]
  580.      */
  581.     public function getSelfies(): Collection
  582.     {
  583.         return $this->selfies;
  584.     }
  585.     public function removeSelfie(string $path): bool
  586.     {
  587.         foreach ($this->getSelfies() as $selfie) {
  588.             if ($path === $selfie->getPath()) {
  589.                 $this->selfies->removeElement($selfie);
  590.                 return true;
  591.             }
  592.         }
  593.         return false;
  594.     }
  595.     public function getConfirmedVideos(): Collection
  596.     {
  597.         return $this->videos->filter(function ($mediaFile): bool {
  598.             if (!$mediaFile instanceof Video) {
  599.                 return false;
  600.             }
  601.             return $mediaFile->isConfirmed();
  602.         });
  603.     }
  604.     /**
  605.      * Храним только 1 видео для анкеты
  606.      */
  607.     public function addVideo(string $videoPath, ?string $posterPath null): Video
  608.     {
  609.         $found $this->getVideos()->filter(function (Video $video) use ($videoPath): bool {
  610.             return $videoPath === $video->getPath();
  611.         });
  612.         if (!$found->isEmpty())
  613.             return $found->first();
  614.         $video = new Video($this$videoPath);
  615.         if (null !== $posterPath) {
  616.             $video->setPreviewPath($posterPath);
  617.         }
  618.         //теперь разрешаем много видео
  619.         //$this->videos->clear();
  620.         $this->videos->add($video);
  621.         return $video;
  622.     }
  623.     /**
  624.      * @return Video[]
  625.      */
  626.     public function getVideos(): Collection
  627.     {
  628.         return $this->videos->filter(function ($mediaFile): bool {
  629.             return ($mediaFile instanceof Video);
  630.         });
  631.     }
  632.     public function removeVideo(string $path): bool
  633.     {
  634.         foreach ($this->getVideos() as $video) {
  635.             if ($path === $video->getPath()) {
  636.                 $this->videos->removeElement($video);
  637.                 $this->photos->removeElement($video);
  638.                 return true;
  639.             }
  640.         }
  641.         return false;
  642.     }
  643.     /**
  644.      * Добавляет таск на обработку оригинала видео в подходящий формат
  645.      *
  646.      * @param string $path Путь к файлу оригинала относительно фс очередей
  647.      */
  648.     public function addRawVideo(string $path): FileProcessingTask
  649.     {
  650.         $file = new FileProcessingTask($this$path);
  651.         $this->processingFiles->add($file);
  652.         return $file;
  653.     }
  654.     public function hasFilesInProcess(): bool
  655.     {
  656.         return $this->videosInProcess() > 0;
  657.     }
  658.     public function videosInProcess(): int
  659.     {
  660.         $inProcess $this->processingFiles->filter(function (FileProcessingTask $task): bool {
  661.             return !$task->isCompleted();
  662.         });
  663.         return $inProcess->count();
  664.     }
  665.     public function isMediaProcessed(): bool
  666.     {
  667.         foreach ($this->videos as $video)
  668.             if (null === $video->getPreviewPath())
  669.                 return false;
  670.         return true;
  671.     }
  672.     public function getAvatar(): ?Avatar
  673.     {
  674.         return $this->avatar;
  675.     }
  676.     public function setAvatar(string $path): void
  677.     {
  678.         $this->avatar = new Avatar($this$path);
  679.     }
  680.     public function removeAvatar(): bool
  681.     {
  682.         if (null == $this->avatar)
  683.             return false;
  684.         foreach ($this->photos as $photo) {
  685.             if ($this->avatar->getPath() === $photo->getPath()) {
  686.                 $this->photos->removeElement($photo);
  687.                 break;
  688.             }
  689.         }
  690.         $this->avatar null;
  691.         return true;
  692.     }
  693.     /**
  694.      * @return CommentByCustomer[]
  695.      */
  696.     public function getComments(): Collection
  697.     {
  698.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  699.             return null == $comment->getParent();
  700.         });
  701.     }
  702.     /**
  703.      * @return CommentByCustomer[]
  704.      */
  705.     public function getCommentsOrderedByNotReplied(): array
  706.     {
  707.         $comments $this->comments->filter(function (CommentByCustomer $comment): bool {
  708.             return null == $comment->getParent();
  709.         })->toArray();
  710.         usort($comments, function (CommentByCustomer $commentACommentByCustomer $commentB): int {
  711.             if ((null == $commentA->getLastCommentByAdvertiser() && null == $commentB->getLastCommentByAdvertiser())
  712.                 || (null != $commentA->getLastCommentByAdvertiser() && null != $commentB->getLastCommentByAdvertiser())) {
  713.                 if ($commentA->getCreatedAt() == $commentB->getCreatedAt())
  714.                     return $commentA->getId() > $commentB->getId() ? -1;
  715.                 else
  716.                     return $commentA->getCreatedAt() > $commentB->getCreatedAt() ? -1;
  717.             }
  718.             if (null == $commentA->getLastCommentByAdvertiser() && null != $commentB->getLastCommentByAdvertiser())
  719.                 return -1;
  720.             else
  721.                 return 1;
  722.         });
  723.         return $comments;
  724.     }
  725.     public function getCreatedAt(): ?\DateTimeImmutable
  726.     {
  727.         return $this->createdAt;
  728.     }
  729.     /**
  730.      * @return CommentByCustomer[]
  731.      */
  732.     public function getCommentsWithoutReply(): Collection
  733.     {
  734.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  735.             return null == $comment->getParent() && false == $comment->isCommentedByAdvertiser();
  736.         });
  737.     }
  738.     /**
  739.      * @return CommentByCustomer[]
  740.      */
  741.     public function getCommentsWithReply(): Collection
  742.     {
  743.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  744.             return null == $comment->getParent() && true == $comment->isCommentedByAdvertiser();
  745.         });
  746.     }
  747.     /**
  748.      * @return CommentByCustomer[]
  749.      */
  750.     public function getNewComments(): Collection
  751.     {
  752.         $weekAgo CarbonImmutable::now()->sub('7 days');
  753.         return $this->comments->filter(function (CommentByCustomer $comment) use ($weekAgo): bool {
  754.             return null == $comment->getParent()
  755.                 && (
  756.                     $comment->getCreatedAt() >= $weekAgo
  757.                     || null == $this->getCommentReply($comment)
  758.                 );
  759.         });
  760.     }
  761.     private function getCommentReply(CommentByCustomer $parent): ?CommentByCustomer
  762.     {
  763.         foreach ($this->comments as $comment)
  764.             if ($comment->getParent() == $parent)
  765.                 return $comment;
  766.         return null;
  767.     }
  768.     public function getOldComments(): Collection
  769.     {
  770.         $weekAgo CarbonImmutable::now()->sub('7 days');
  771.         return $this->comments->filter(function (CommentByCustomer $comment) use ($weekAgo): bool {
  772.             return null == $comment->getParent()
  773.                 && false == (
  774.                     $comment->getCreatedAt() >= $weekAgo
  775.                     || null == $this->getCommentReply($comment)
  776.                 );
  777.         });
  778.     }
  779.     public function getCommentFromUser(Customer $user): ?CommentByCustomer
  780.     {
  781.         foreach ($this->comments as $comment)
  782.             if (null == $comment->getParent() && null != $comment->getUser() && $user->getId() == $comment->getUser()->getId())
  783.                 return $comment;
  784.         return null;
  785.     }
  786.     //TODO return type
  787.     public function getCity(): City
  788.     {
  789.         return $this->city;
  790.     }
  791.     /**
  792.      * @return Station[]
  793.      */
  794.     public function getStations(): Collection
  795.     {
  796.         return $this->stations;
  797.     }
  798.     public function getMapCoordinate(): ?MapCoordinate
  799.     {
  800.         return $this->mapCoordinate;
  801.     }
  802.     public function getUpdatedAt(): ?\DateTimeImmutable
  803.     {
  804.         return $this->updatedAt;
  805.     }
  806.     public function setUpdatedAt(\DateTimeImmutable $updatedAt): void
  807.     {
  808.         $this->updatedAt $updatedAt;
  809.     }
  810.     public function getModerationStatus(): int
  811.     {
  812.         return $this->moderationStatus;
  813.     }
  814.     public function setModerationStatus(int $status): void
  815.     {
  816.         if (self::MODERATION_STATUS_APPROVED === $status) {
  817.             throw new \RuntimeException(sprintf('Use %s::passModeration() method instead', static::class));
  818.         }
  819.         $validStatuses = [self::MODERATION_STATUS_NOT_PASSEDself::MODERATION_STATUS_APPROVEDself::MODERATION_STATUS_WAITINGself::MODERATION_STATUS_REJECTED];
  820.         if (false === array_search($status$validStatuses))
  821.             throw new \LogicException('Trying to set an invalid moderation status');
  822.         $this->moderationStatus $status;
  823.     }
  824.     public function isModerationPassed(): bool
  825.     {
  826.         return $this->moderationStatus == self::MODERATION_STATUS_APPROVED;
  827.     }
  828.     public function isModerationWaiting(): bool
  829.     {
  830.         return $this->moderationStatus == self::MODERATION_STATUS_WAITING;
  831.     }
  832.     public function isModerationRejected(): bool
  833.     {
  834.         return $this->moderationStatus == self::MODERATION_STATUS_REJECTED;
  835.     }
  836.     public function passModeration(?ModerationRequest $moderationRequest null): void
  837.     {
  838.         $this->moderationStatus self::MODERATION_STATUS_APPROVED;
  839.         $func = static function ($kPhoto|Video $file) use ($moderationRequest): bool {
  840.             if (!$file->isConfirmed()) {
  841.                 $file->passModeration($moderationRequest);
  842.             }
  843.             return true;
  844.         };
  845.         $this->videos->forAll($func);
  846.     }
  847.     public function delete(): void
  848.     {
  849.         $this->deletePlacementHiding();
  850.         $this->deleteFromAdBoard();
  851.         $now = new \DateTimeImmutable('now');
  852.         $toDelete = [];
  853.         foreach ($this->topPlacements as $topPlacement) {
  854.             if ($topPlacement->getExpiresAt() > $now)
  855.                 $toDelete[] = $topPlacement;
  856.         }
  857.         foreach ($toDelete as $topPlacement)
  858.             $this->topPlacements->removeElement($topPlacement);
  859.         $this->setDeletedAt(Carbon::now());
  860.     }
  861.     public function deletePlacementHiding(): void
  862.     {
  863.         $this->placementHiding null;
  864.     }
  865.     public function deleteFromAdBoard(): void
  866.     {
  867.         $this->adBoardPlacement null;
  868.     }
  869.     //TODO return type
  870.     public function undoDelete(): void
  871.     {
  872.         $this->setDeletedAt(); // will pass null by default
  873.     }
  874.     //TODO return type
  875.     public function deleteFromTopPlacement(): void
  876.     {
  877.         //здесь нужна логика отмены конретного размещения
  878. //        $this->topPlacement = null;
  879.     }
  880.     public function getExpressPricing(): ?ExpressPricing
  881.     {
  882.         return $this->expressPricing;
  883.     }
  884.     public function getCarPricing(): ?CarPricing
  885.     {
  886.         return $this->carPricing;
  887.     }
  888.     //TODO return type
  889.     /**
  890.      * @return int[]
  891.      */
  892.     public function getClientTypes(): array
  893.     {
  894.         return $this->clientTypes ?? [];
  895.     }
  896.     /**
  897.      * @param int[] $clientTypes
  898.      */
  899.     public function setClientTypes(array $clientTypes): void
  900.     {
  901.         $this->clientTypes $clientTypes;
  902.     }
  903.     public function getMessengers(): ?Messengers
  904.     {
  905.         return $this->messengers;
  906.     }
  907.     public function getInactivatedAt(): ?\DateTimeImmutable
  908.     {
  909.         return $this->inactivatedAt;
  910.     }
  911.     public function setInactive(): void
  912.     {
  913.         $this->inactivatedAt CarbonImmutable::now();
  914.     }
  915.     public function undoInactive(): void
  916.     {
  917.         $this->inactivatedAt null;
  918.     }
  919.     public function isHidden(): bool
  920.     {
  921.         return null !== $this->getPlacementHiding();
  922.     }
  923.     public function getPlacementHiding(): ?PlacementHiding
  924.     {
  925.         return $this->placementHiding;
  926.     }
  927.     public function setPlacementHiding(PlacementHiding $placementHiding): void
  928.     {
  929.         $this->placementHiding $placementHiding;
  930.     }
  931.     public function hasSelfie(): bool
  932.     {
  933.         return $this->selfies->count() > 0;
  934.     }
  935.     public function hasVideo(): bool
  936.     {
  937.         return $this->videos->count() > 0;
  938.     }
  939.     public function isCommented(): bool
  940.     {
  941.         return $this->comments->count() > 0;
  942.     }
  943.     public function adminApprovalPhoto(): ?AdminApprovalPhoto
  944.     {
  945.         return $this->adminApprovalPhoto;
  946.     }
  947.     public function setAdminApprovalPhoto(?string $path): void
  948.     {
  949.         $this->adminApprovalPhoto $path ? new AdminApprovalPhoto($this$path) : null;
  950.     }
  951.     public function seo(): ?array
  952.     {
  953.         return $this->seo;
  954.     }
  955.     public function seoPhoneNumber(): ?string
  956.     {
  957.         return $this->seo['phone'] ?? null;
  958.     }
  959.     public function setSeoPhoneNumber(string $phoneNumber): void
  960.     {
  961.         if (null === $this->seo) {
  962.             $this->seo = [];
  963.         }
  964.         $this->seo['phone'] = $phoneNumber;
  965.     }
  966.     public function getPrimaryStation(): ?Station
  967.     {
  968.         $station $this->primaryStation ?? $this->getStations()->first();
  969.         if (false === $station) {
  970.             return null;
  971.         }
  972.         return $station;
  973.     }
  974.     public function setPrimaryStation(?Station $station): void
  975.     {
  976.         $this->primaryStation $station;
  977.         $this->normalizePrimaryStation();
  978.     }
  979.     public function getStationsSortedByPrimary(): array
  980.     {
  981.         $stations $this->stations->toArray();
  982.         if (!$this->primaryStation) {
  983.             return $stations;
  984.         }
  985.         usort($stations, function (Station $aStation $b) {
  986.             if ($a->getId() === $this->primaryStation->getId()) return -1;
  987.             if ($b->getId() === $this->primaryStation->getId()) return 1;
  988.             return 0;
  989.         });
  990.         return $stations;
  991.     }
  992.     public function getDeleteMode(): int
  993.     {
  994.         return $this->deleteMode;
  995.     }
  996.     public function setDeleteMode(int $deleteMode): self
  997.     {
  998.         $this->deleteMode $deleteMode;
  999.         return $this;
  1000.     }
  1001.     public function isHardDeleted(): bool
  1002.     {
  1003.         return $this->deleteMode === 2;
  1004.     }
  1005. }