|
|
@@ -0,0 +1,180 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Service\Typo3;
|
|
|
+
|
|
|
+use App\Entity\Organization\Organization;
|
|
|
+use App\Entity\Organization\Subdomain;
|
|
|
+use App\Message\Command\MailerCommand;
|
|
|
+use App\Message\Command\Typo3\Typo3UpdateCommand;
|
|
|
+use App\Repository\Access\AccessRepository;
|
|
|
+use App\Repository\Organization\SubdomainRepository;
|
|
|
+use App\Service\Mailer\Model\SubdomainChangeModel;
|
|
|
+use App\Service\Organization\Utils as OrganizationUtils;
|
|
|
+use Doctrine\ORM\EntityManagerInterface;
|
|
|
+use Symfony\Bundle\SecurityBundle\Security;
|
|
|
+use Symfony\Component\Console\Exception\InvalidArgumentException;
|
|
|
+use Symfony\Component\Messenger\MessageBusInterface;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Service de gestion des sous-domaines des utilisateurs
|
|
|
+ */
|
|
|
+class SubdomainService
|
|
|
+{
|
|
|
+ // Max number of subdomains that an organization can own
|
|
|
+ const MAX_SUBDOMAINS_NUMBER = 3;
|
|
|
+
|
|
|
+ const RX_VALIDATE_SUBDOMAIN = '/^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/';
|
|
|
+
|
|
|
+ public function __construct(
|
|
|
+ private readonly SubdomainRepository $subdomainRepository,
|
|
|
+ private readonly EntityManagerInterface $em,
|
|
|
+ private readonly MessageBusInterface $messageBus,
|
|
|
+ private readonly Security $security,
|
|
|
+ private readonly OrganizationUtils $organizationUtils,
|
|
|
+ private readonly BindFileService $bindFileService,
|
|
|
+ private readonly AccessRepository $accessRepository
|
|
|
+ ) {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Is the organization allowed to register a new subdomain
|
|
|
+ *
|
|
|
+ * @param Organization $organization
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ public function canRegisterNewSubdomain(Organization $organization): bool {
|
|
|
+ return count($organization->getSubdomains()) < self::MAX_SUBDOMAINS_NUMBER;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Is the input a valid value for a subdomain
|
|
|
+ *
|
|
|
+ * @param string $subdomainValue
|
|
|
+ * @return false|int
|
|
|
+ */
|
|
|
+ public function isValidSubdomain(string $subdomainValue): bool
|
|
|
+ {
|
|
|
+ return (bool)preg_match(self::RX_VALIDATE_SUBDOMAIN, $subdomainValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Register a new subdomain for the organization
|
|
|
+ * Is $activate is true, makes this new subdomain the active one too.
|
|
|
+ *
|
|
|
+ * @param Organization $organization
|
|
|
+ * @param string $subdomainValue
|
|
|
+ * @param bool $activate
|
|
|
+ * @return Subdomain
|
|
|
+ */
|
|
|
+ public function addNewSubdomain(
|
|
|
+ Organization $organization,
|
|
|
+ string $subdomainValue,
|
|
|
+ bool $activate = false
|
|
|
+ ): Subdomain {
|
|
|
+
|
|
|
+ if (!$this->isValidSubdomain($subdomainValue)) {
|
|
|
+ throw new \RuntimeException("Not a valid subdomain");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$this->canRegisterNewSubdomain($organization)) {
|
|
|
+ throw new \RuntimeException("This organization can not register new subdomains");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Vérifie que le sous-domaine n'est pas déjà utilisé
|
|
|
+ if ($this->subdomainRepository->findBy(['subdomain' => $subdomainValue])) {
|
|
|
+ throw new InvalidArgumentException('This subdomain is already registered');
|
|
|
+ }
|
|
|
+
|
|
|
+ $subdomain = new Subdomain();
|
|
|
+ $subdomain->setSubdomain($subdomainValue);
|
|
|
+ $subdomain->setOrganization($organization);
|
|
|
+ $subdomain->setActive(false);
|
|
|
+
|
|
|
+ $this->em->persist($subdomain);
|
|
|
+ $this->em->flush();
|
|
|
+
|
|
|
+ // Register into the BindFile (takes up to 5min to take effect)
|
|
|
+ $this->bindFileService->registerSubdomain($subdomain->getSubdomain());
|
|
|
+
|
|
|
+ if ($activate) {
|
|
|
+ $subdomain = $this->activateSubdomain($subdomain);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $subdomain;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Makes the $subdomain the active one for the organization.
|
|
|
+ *
|
|
|
+ * @param Subdomain $subdomain
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function activateSubdomain(Subdomain $subdomain): Subdomain {
|
|
|
+ if ($subdomain->isActive()) {
|
|
|
+ throw new \RuntimeException('The subdomain is already active');
|
|
|
+ }
|
|
|
+ if (!$subdomain->getId()) {
|
|
|
+ throw new \RuntimeException('Can not activate a non-persisted subdomain');
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($subdomain->getOrganization()->getSubdomains() as $other) {
|
|
|
+ if ($other !== $subdomain && $other->isActive()) {
|
|
|
+ $other->setActive(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $subdomain->setActive(true);
|
|
|
+
|
|
|
+ // TODO: comprendre pourquoi ce refresh est indispensable pour que l'organisation soit à jour
|
|
|
+ $this->em->flush();
|
|
|
+ $this->em->refresh($subdomain->getOrganization());
|
|
|
+
|
|
|
+ $this->renameAdminUserToMatchSubdomain($subdomain);
|
|
|
+
|
|
|
+ // Update the typo3 website (asynchronously with messenger)
|
|
|
+ $this->messageBus->dispatch(
|
|
|
+ new Typo3UpdateCommand($subdomain->getOrganization()->getId())
|
|
|
+ );
|
|
|
+
|
|
|
+ // Send confirmation email
|
|
|
+ $this->sendConfirmationEmail($subdomain);
|
|
|
+
|
|
|
+ return $subdomain;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Rename the admin user of the organization to match the given subdomain
|
|
|
+ *
|
|
|
+ * @param Organization $organization
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ protected function renameAdminUserToMatchSubdomain(Subdomain $subdomain): void {
|
|
|
+ $adminAccess = $this->accessRepository->findOneBy([
|
|
|
+ 'adminAccess' => 1,
|
|
|
+ 'organization' => $subdomain->getOrganization()
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $adminAccess->getPerson()->setUsername('admin' . $subdomain->getSubdomain());
|
|
|
+ $this->em->flush();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Send the confirmation email to the organization after a new subdomain has been activated
|
|
|
+ *
|
|
|
+ * @param Subdomain $subdomain
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ protected function sendConfirmationEmail(Subdomain $subdomain): void {
|
|
|
+ // TODO: revoir quel sender par défaut
|
|
|
+ $senderId = $this->security->getUser() ? $this->security->getUser()->getId() : 211;
|
|
|
+
|
|
|
+ $mailModel = (new SubdomainChangeModel())
|
|
|
+ ->setSenderId($senderId)
|
|
|
+ ->setOrganizationId($subdomain->getOrganization()->getId())
|
|
|
+ ->setSubdomainId($subdomain->getId())
|
|
|
+ ->setUrl($this->organizationUtils->getOrganizationWebsite($subdomain->getOrganization()));
|
|
|
+
|
|
|
+ // Envoi d'un email
|
|
|
+ $this->messageBus->dispatch(
|
|
|
+ new MailerCommand($mailModel)
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|