소스 검색

add the ot:site:setdomain CLI and API command

Olivier Massot 4 년 전
부모
커밋
fcec7253b6

+ 78 - 0
ot_admin/Classes/Command/SetSiteDomainCommand.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Opentalent\OtAdmin\Command;
+
+
+use Opentalent\OtAdmin\Controller\SiteController;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+
+/**
+ * This CLI command sets a new domain for the organization website
+ *
+ * @package Opentalent\OtAdmin\Command
+ */
+class SetSiteDomainCommand extends Command
+{
+    /**
+     * -- This method is expected by Typo3, do not rename ou remove --
+     *
+     * Allows to configure the command.
+     * Allows to add a description, a help text, and / or define arguments.
+     *
+     */
+    protected function configure()
+    {
+        $this
+            ->setName("ot:site:setdomain")
+            ->setDescription("Set a new domain for the organization website")
+            ->setHelp("Set a new domain for the organization website. If the --redirect argument is passed, " .
+                           "a redirection will be added from the existing domain to the new one.")
+            ->addArgument(
+                'organization_id',
+                InputArgument::REQUIRED,
+                "The organization's id in the opentalent DB"
+            )
+            ->addArgument(
+                'domain',
+                InputArgument::REQUIRED,
+                "The new domain to set up"
+            )
+            ->addOption(
+                'redirect',
+                'r',
+                InputOption::VALUE_NONE,
+                'Use this option to add a redirection from the previous domain to the new one'
+            );
+    }
+
+    /**
+     * -- This method is expected by Typo3, do not rename ou remove --
+     *
+     * Executes the command for creating the new organization
+     *
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @throws \Exception
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $org_id = $input->getArgument('organization_id');
+        $domain = $input->getArgument('domain');
+        $redirect = $input->getOption('redirect');
+
+        $io = new SymfonyStyle($input, $output);
+
+        $siteController = GeneralUtility::makeInstance(ObjectManager::class)->get(SiteController::class);
+        $rootUid = $siteController->setSiteDomainAction($org_id, $domain, $redirect);
+
+        $io->success(sprintf("The website with root uid " . $rootUid . " domain has been set to " . $domain));
+    }
+
+}

+ 116 - 50
ot_admin/Classes/Controller/SiteController.php

@@ -490,7 +490,7 @@ class SiteController extends ActionController
                 ->execute();
 
             // Create the site config.yaml file
-            $this->writeConfigFile($organizationId, $rootUid, $domain);
+            $this->writeConfigFile($organizationId, $rootUid, $domain, true);
 
             // Create the user_upload directory and update the sys_filemounts table
             $uploadRelPath = "/user_upload/" . $organizationId;
@@ -1184,6 +1184,36 @@ class SiteController extends ActionController
             $warnings);
     }
 
+    /**
+     * Set a new domain for the website.
+     * If $redirect is true, also add a redirection from the former domain to the new one
+     *
+     * @param int $organizationId
+     * @param string $newDomain
+     * @param bool $redirect
+     * @return int
+     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
+     * @throws NoSuchWebsiteException
+     */
+    public function setSiteDomainAction(int $organizationId, string $newDomain, bool $redirect = true): int
+    {
+        if (!preg_match(
+                "/([a-z0-9A-Z]\.)*[a-z0-9-]+\.([a-z0-9]{2,24})+(\.co\.([a-z0-9]{2,24})|\.([a-z0-9]{2,24}))*\/?/",
+                $newDomain
+            ) && !preg_match(
+                "/[a-z0-9A-Z-]+\//",
+                $newDomain
+            )
+        ) {
+            throw new \RuntimeException("The given domain does not seems to be a valid domain: " . $newDomain);
+        }
+
+        $rootUid = $this->findRootUidFor($organizationId);
+        $this->writeConfigFile($organizationId, $rootUid, $newDomain);
+
+        return $rootUid;
+    }
+
     /**
      * Retrieve the Organization object from the repository and then,
      * from the Opentalent API
@@ -1506,9 +1536,9 @@ class SiteController extends ActionController
      *
      * @param int $organizationId
      * @param int $rootUid
-     * @return array   Configuration of the website
+     * @return array   Path of the configuration file and parsed configuration of the website
      */
-    protected function findConfigFor(int $organizationId, int $rootUid) {
+    protected function findConfigFileAndContentFor(int $organizationId, int $rootUid) {
 
         $configs_directory = $_ENV['TYPO3_PATH_APP'] . "/config/sites/";
         $candidates = scandir($configs_directory);
@@ -1520,7 +1550,7 @@ class SiteController extends ActionController
                 $yamlConfig = Yaml::parseFile($filename);
 
                 if ($yamlConfig['rootPageId'] === $rootUid) {
-                    return $yamlConfig;
+                    return [$filename, $yamlConfig];
                 }
             }
         }
@@ -1529,77 +1559,114 @@ class SiteController extends ActionController
         foreach ($candidates as $subdir) {
             $yamlConfig = Yaml::parseFile($filename);
             if ($yamlConfig['rootPageId'] === $rootUid) {
-                return $yamlConfig;
+                return [$filename, $yamlConfig];
             }
         }
 
-        throw new \RuntimeException("No config file found for this website");
+        return [null, []];
+    }
+
+    /**
+     * Similar to findConfigFileAndContentFor(), but only returns the parsed configuration
+     * @param int $organizationId
+     * @param int $rootUid
+     * @return array   Configuration of the website
+     */
+    protected function findConfigFor(int $organizationId, int $rootUid) {
+        $pathAndConfig = $this->findConfigFileAndContentFor($organizationId, $rootUid);
+        return $pathAndConfig[1];
     }
 
     /**
-     * Write the .../sites/.../config.yaml file of the given site
+     * Similar to findConfigFileAndContentFor(), but only returns the config file path
+     * @param int $organizationId
+     * @param int $rootUid
+     * @return string   Path of the config file of the given website
+     */
+    protected function findConfigFilePathFor(int $organizationId, int $rootUid) {
+        $pathAndConfig = $this->findConfigFileAndContentFor($organizationId, $rootUid);
+        return $pathAndConfig[0];
+    }
+
+    /**
+     * Create or update the .../sites/.../config.yaml file of the given site
      *
      * @param int $organizationId
      * @param int $rootUid
      * @param string $domain
+     * @param bool $forceRecreate
      * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
      */
-    private function writeConfigFile(int $organizationId, int $rootUid, string $domain) {
+    private function writeConfigFile(int $organizationId, int $rootUid, string $domain, bool $forceRecreate = false) {
+
+        $existing = $this->findConfigFileAndContentFor($organizationId, $rootUid);
+        $configFilename = $existing[0];
+        $config = $existing[1];
 
         $subdomain = explode('.', $domain)[0];
 
-        $config_dir = $_ENV['TYPO3_PATH_APP'] . "/config/sites/" . $subdomain . '_' . $organizationId;
-        $config_filename = $config_dir . "/config.yaml";
+        if (!$configFilename) {
+            $configDir = $_ENV['TYPO3_PATH_APP'] . "/config/sites/" . $subdomain . '_' . $organizationId;
+            $configFilename = $configDir . "/config.yaml";
+            $isNew = true;
 
-        if (file_exists($config_filename)) {
-            throw new \RuntimeException("A file named " . $config_filename . " already exists. Abort.");
+            if (file_exists($configFilename)) {
+                throw new \RuntimeException("A file named " . $configFilename . " already exists. Abort.");
+            }
+        } else {
+            $configDir = dirname($configFilename);
+            $config['base'] = 'https://' . $domain;
+            $isNew = false;
+        }
+
+        if ($isNew || $forceRecreate) {
+            $config = ['base' => 'https://' . $domain,
+                'baseVariants' => [
+                    ['base' => $subdomain . '/',
+                        'condition' => 'applicationContext == "Development"']
+                ],
+                'errorHandling' => [
+                    ['errorCode' => '404',
+                        'errorHandler' => 'PHP',
+                        'errorPhpClassFQCN' => 'Opentalent\OtTemplating\Page\ErrorHandler'],
+                    ['errorCode' => '403',
+                        'errorHandler' => 'PHP',
+                        'errorPhpClassFQCN' => 'Opentalent\OtTemplating\Page\ErrorHandler'],
+                ],
+                'flux_content_types' => '',
+                'flux_page_templates' => '',
+                'languages' => [[
+                    'title' => 'Fr',
+                    'enabled' => True,
+                    'base' => '/',
+                    'typo3Language' => 'fr',
+                    'locale' => 'fr_FR',
+                    'iso-639-1' => 'fr',
+                    'navigationTitle' => 'Fr',
+                    'hreflang' => 'fr-FR',
+                    'direction' => 'ltr',
+                    'flag' => 'fr',
+                    'languageId' => '0',
+                ]],
+                'rootPageId' => $rootUid,
+                'routes' => []
+            ];
         }
 
-        $config = ['base' => 'https://' . $domain,
-            'baseVariants'=>[
-                ['base' => $subdomain . '/',
-                 'condition' => 'applicationContext == "Development"']
-            ],
-            'errorHandling'=>[
-                ['errorCode'=>'404',
-                 'errorHandler'=>'PHP',
-                 'errorPhpClassFQCN'=>'Opentalent\OtTemplating\Page\ErrorHandler'],
-                ['errorCode'=>'403',
-                 'errorHandler'=>'PHP',
-                 'errorPhpClassFQCN'=>'Opentalent\OtTemplating\Page\ErrorHandler'],
-            ],
-            'flux_content_types'=>'',
-            'flux_page_templates'=>'',
-            'languages'=>[[
-                'title'=>'Fr',
-                'enabled'=>True,
-                'base'=>'/',
-                'typo3Language'=>'fr',
-                'locale'=>'fr_FR',
-                'iso-639-1'=>'fr',
-                'navigationTitle'=>'Fr',
-                'hreflang'=>'fr-FR',
-                'direction'=>'ltr',
-                'flag'=>'fr',
-                'languageId'=>'0',
-            ]],
-            'rootPageId'=>$rootUid,
-            'routes'=>[]
-        ];
         $yamlConfig = Yaml::dump($config, 99, 2);
 
-        if (!file_exists($config_dir)) {
-            $this->mkDir($config_dir);
+        if (!file_exists($configDir)) {
+            $this->mkDir($configDir);
         }
-        GeneralUtility::writeFile($config_filename, $yamlConfig);
+        GeneralUtility::writeFile($configFilename, $yamlConfig);
 
         // Set the owner and mods, in case www-data is not the one who run this command
         // @see https://www.php.net/manual/fr/function.stat.php
         try {
             $stats = stat($_ENV['TYPO3_PATH_APP'] . '/public/index.php');
-            chown($config_filename, $stats['4']);
-            chgrp($config_filename, $stats['5']);
-            chmod($config_filename, $stats['2']);
+            chown($configFilename, $stats['4']);
+            chgrp($configFilename, $stats['5']);
+            chmod($configFilename, $stats['2']);
         } catch (\TYPO3\CMS\Core\Error\Exception $e) {
         }
 
@@ -1609,7 +1676,6 @@ class SiteController extends ActionController
         $cacheSystem->remove('pseudo-sites');
     }
 
-
     /**
      * Create the BE user for the website
      * The user shall be already created in the Opentalent DB

+ 34 - 0
ot_admin/Classes/Http/ApiController.php

@@ -246,6 +246,40 @@ class ApiController implements LoggerAwareInterface
         );
     }
 
+    /**
+     * -- Target of the route 'site_set_domain' --
+     * >> Requires a query param named 'organization-id' (int)
+     *
+     * Set a new domain for the organization website
+     *
+     * @param ServerRequest $request
+     * @return JsonResponse
+     * @throws \Exception
+     */
+    public function setSiteDomainAction(ServerRequest $request) {
+        $this->assertIpAllowed();
+
+        $organizationId = $this->getOrganizationId($request);
+
+        $queryParams = $request->getQueryParams();
+        $domain = $queryParams['domain'];
+        if (!$domain) {
+            throw new \RuntimeException("Missing 'domain' parameter");
+        }
+        $redirect = (isset($queryParams['redirect']) && $queryParams['redirect']);
+
+        $controller = GeneralUtility::makeInstance(ObjectManager::class)->get(SiteController::class);
+        $rootUid = $controller->setSiteDomainAction($organizationId, $domain, $redirect);
+
+        return new JsonResponse(
+            [
+                'organization_id' => $organizationId,
+                'msg' => "The cache has been cleared for the website with root uid " . $rootUid . "",
+                'root_uid' => $rootUid
+            ]
+        );
+    }
+
     /**
      * -- Target of the route 'site_status' --
      * >> Requires a query param named 'organization-id' (int)

+ 5 - 0
ot_admin/Configuration/Backend/Routes.php

@@ -37,6 +37,11 @@ return [
         'target' => ApiController::class . '::clearSiteCacheAction',
         'access' => 'public'
     ],
+    'site_setdomain' => [
+        'path' => '/otadmin/site/set-domain',
+        'target' => ApiController::class . '::setSiteDomainAction',
+        'access' => 'public'
+    ],
     'site_status' => [
         'path' => '/otadmin/site/status',
         'target' => ApiController::class . '::getSiteStatusAction',

+ 3 - 0
ot_admin/Configuration/Commands.php

@@ -28,6 +28,9 @@ return [
     'ot:site:clear-cache' => [
         'class' => Opentalent\OtAdmin\Command\ClearSiteCacheCommand::class
     ],
+    'ot:site:setdomain' => [
+        'class' => Opentalent\OtAdmin\Command\SetSiteDomainCommand::class
+    ],
     'ot:site:status' => [
         'class' => Opentalent\OtAdmin\Command\GetSiteStatusCommand::class
     ],

+ 6 - 0
ot_admin/Configuration/Services.yaml

@@ -37,6 +37,12 @@ services:
         command: 'ot:site:clear-cache'
         schedulable: true
 
+  Opentalent\OtAdmin\Command\SetSiteDomainCommand:
+    tags:
+      - name: 'ot:site:setdomain'
+        command: 'ot:site:setdomain'
+        schedulable: true
+
   Opentalent\OtAdmin\Command\GetSiteStatusCommand:
     tags:
       - name: 'ot:site:status'