getSubdomains()) < self::MAX_SUBDOMAINS_NUMBER; } /** * Is the input a valid value for a subdomain * * @see https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2 * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5 * @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1 * * @param string $subdomainValue * @return bool */ public function isValidSubdomain(string $subdomainValue): bool { return (bool)preg_match(self::RX_VALIDATE_SUBDOMAIN, $subdomainValue); } /** * Is the subdomain a reserved one * @see https://ressources.opentalent.fr/display/SPEC/Nom+de+sous+domaines+reserves+pour+2IOS * * @param string $subdomainValue * @return bool * @throws \Exception */ public function isReservedSubdomain(string $subdomainValue): bool { // $reservedSubdomains = $this->configUtils->get('subdomains')['reserved']; dd($this->parameterBag->get('modules')); $reservedSubdomains = $this->parameterBag->get('opentalent')['reserved_subdomains']; $subRegexes = array_map( function (string $s) { return '(' . trim($s, '^$/\s') . ')'; }, $reservedSubdomains ); $regex = '/^' . strtolower(implode("|", $subRegexes)) . '$/'; return preg_match($regex, $subdomainValue) !== 0; } /** * 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"); } if ($this->isReservedSubdomain($subdomainValue)) { throw new \RuntimeException('This subdomain is not available'); } // Vérifie que le sous-domaine n'est pas déjà utilisé if ($this->subdomainRepository->findBy(['subdomain' => $subdomainValue])) { throw new \RuntimeException('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 Subdomain */ 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'); } $subdomain = $this->setOrganizationActiveSubdomain($subdomain); $this->renameAdminUserToMatchSubdomain($subdomain); // Update the typo3 website (asynchronously with messenger) $this->updateTypo3Website($subdomain->getOrganization()); // Send confirmation email $this->sendConfirmationEmail($subdomain); return $subdomain; } /** * The subdomain becomes the only active subdomain of its organization. * New state is persisted is database. * * @param Subdomain $subdomain * @return Subdomain */ protected function setOrganizationActiveSubdomain(Subdomain $subdomain): 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()); return $subdomain; } /** * Rename the admin user of the organization to match the given subdomain * * @param Subdomain $subdomain * @return void */ protected function renameAdminUserToMatchSubdomain(Subdomain $subdomain): void { $adminAccess = $this->accessRepository->findAdminAccess($subdomain->getOrganization()); $adminAccess->getPerson()->setUsername('admin' . $subdomain->getSubdomain()); $this->em->flush(); } /** * Trigger an update of the typo3 organization's website * * @param $organization * @return void */ protected function updateTypo3Website(Organization $organization): void { $this->messageBus->dispatch( new Typo3UpdateCommand($organization->getId()) ); } /** * Build the data model for the confirmation email * * @param Subdomain $subdomain * @return SubdomainChangeModel */ protected function getMailModel(Subdomain $subdomain): SubdomainChangeModel { $adminAccess = $this->accessRepository->findAdminAccess($subdomain->getOrganization()); /** @phpstan-ignore-next-line */ return (new SubdomainChangeModel()) ->setOrganizationId($subdomain->getOrganization()->getId()) ->setSubdomainId($subdomain->getId()) ->setUrl($this->organizationUtils->getOrganizationWebsite($subdomain->getOrganization())) ->setSenderId($adminAccess->getId()); } /** * 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 $model = $this->getMailModel($subdomain); // Envoi d'un email $this->messageBus->dispatch( new MailerCommand($model) ); } }