Browse Source

add the new ot_admin extension [ongoing]

Olivier Massot 5 years ago
parent
commit
2eb260b11c

+ 69 - 0
ot_admin/Classes/Command/CreateOrganizationCommand.php

@@ -0,0 +1,69 @@
+<?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\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * This CLI command creates an organization's website
+ * by fetching its latest data from the Opentalent API
+ *
+ * @package Opentalent\OtAdmin\Command
+ */
+class CreateOrganizationCommand 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:create")
+            ->setDescription("Create an organization's website " .
+                                       "by fetching its latest data from the Opentalent API")
+            ->setHelp("Call this method by giving it the organization's id in the Opentalent DB. 
+                            If no site exists, create it; 
+                            If a site already exists, do nothing.")
+            ->addArgument(
+                'organization_id',
+                InputArgument::REQUIRED,
+                "The organization's id in the opentalent DB"
+            );
+    }
+
+    /**
+     * -- 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
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        // Make sure the _cli_ user is loaded
+        // TYPO3\CMS\Core\Core\Bootstrap::initializeBackendAuthentication();
+
+        $org_id = $input->getArgument('organization_id');
+
+        $io = new SymfonyStyle($input, $output);
+
+        $siteController = new SiteController();
+        $rootUid = $siteController->createSiteAction($org_id);
+
+        $io->success(sprintf("A new website has been created with root page uid=" . $rootUid));
+    }
+
+}

+ 539 - 0
ot_admin/Classes/Controller/SiteController.php

@@ -0,0 +1,539 @@
+<?php
+
+namespace Opentalent\OtAdmin\Controller;
+
+use Opentalent\OtTemplating\Domain\Model\Organization;
+use Opentalent\OtTemplating\Domain\Repository\OrganizationRepository;
+use Opentalent\OtTemplating\Exception\ApiRequestException;
+use Psr\Log\LoggerAwareInterface;
+use Symfony\Component\Yaml\Yaml;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+
+
+/**
+ * The SiteController implements some admin-only operations
+ * on Typo3 websites, like creation or update.
+ */
+class SiteController extends ActionController
+{
+    // Templates names
+    const TEMPLATE_HOME = "OpenTalent.OtTemplating->home";
+    const TEMPLATE_1COL = "OpenTalent.OtTemplating->1Col";
+    const TEMPLATE_3COL = "OpenTalent.OtTemplating->home";
+    const TEMPLATE_EVENTS = "OpenTalent.OtTemplating->events";
+    const TEMPLATE_STRUCTURESEVENTS = "OpenTalent.OtTemplating->structuresEvents";
+    const TEMPLATE_STRUCTURES = "OpenTalent.OtTemplating->structures";
+    const TEMPLATE_CONTACT = "OpenTalent.OtTemplating->contact";
+    const TEMPLATE_NEWS = "OpenTalent.OtTemplating->news";
+    const TEMPLATE_MEMBERS = "OpenTalent.OtTemplating->members";
+    const TEMPLATE_MEMBERSCA = "OpenTalent.OtTemplating->membersCa";
+    const TEMPLATE_E404 = "OpenTalent.OtTemplating->e404";
+
+    // Pages dokType values
+    const DOK_PAGE = 1;
+    const DOK_SHORTCUT = 4;
+    const DOK_FOLDER = 116;
+
+    // Contents CTypes
+    const CTYPE_TEXT = 'text';
+    const CTYPE_IMAGE = 'image';
+    const CTYPE_TEXTPIC = 'textpic';
+    const CTYPE_TEXTMEDIA = 'textmedia';
+    const CTYPE_HTML = 'html';
+    const CTYPE_HEADER = 'header';
+    const CTYPE_UPLOADS = 'uploads';
+    const CTYPE_LIST = 'list';
+    const CTYPE_SITEMAP = 'menu_sitemap';
+
+//    const DEFAULT_ROOT_PID = 134833;
+    const DEFAULT_ROOT_PID = 11;
+
+    // Default values
+    const DEFAULT_THEME = 'Classic';
+    const DEFAULT_COLOR = 'light-blue';
+
+    /**
+     * Doctrine connection pool
+     * @var object|LoggerAwareInterface|\TYPO3\CMS\Core\SingletonInterface
+     */
+    private $cnnPool;
+
+    /**
+     * Index of the pages created
+     * >> [slug => uid]
+     * @var array
+     */
+    private $createdPagesIndex;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->cnnPool = GeneralUtility::makeInstance(ConnectionPool::class);
+        $this->createdPagesIndex = [];
+    }
+
+    /**
+     * Creates a new website for the given organization, and
+     * returns the root page uid of the newly created site
+     *
+     * @param int $organizationId
+     * @return int Uid of the root page of the newly created website
+     * @throws \RuntimeException
+     */
+    public function createSiteAction(int $organizationId) {
+
+        // ** Get the organization object from the Opentalent API
+        $manager = GeneralUtility::makeInstance(ObjectManager::class);
+        $organizationRepository = GeneralUtility::makeInstance(
+            OrganizationRepository::class,
+            $manager
+        );
+        try {
+            $organization = $organizationRepository->findById($organizationId);
+        } catch (ApiRequestException $e) {
+            throw new \RuntimeException('Unable to fetch the organization with id: ' . $organizationId);
+        }
+
+        // ** Test the existence of a website with this name and or organization id
+
+        // Is there a site with this organization's name?
+        $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages');
+        $queryBuilder
+            ->select('uid')
+            ->from('pages')
+            ->where($queryBuilder->expr()->eq('title', $queryBuilder->createNamedParameter($organization->getName())))
+            ->andWhere('is_siteroot=1');
+        $statement = $queryBuilder->execute();
+
+        if ($statement->rowCount() > 0) {
+            throw new \RuntimeException('A website with this name already exists: ' . $organization->getName());
+        }
+
+        // Is there a site with this organization's id?
+        $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages');
+        $statement = $queryBuilder
+            ->select('uid')
+            ->from('pages')
+            ->where($queryBuilder->expr()->eq('tx_opentalent_structure_id', $queryBuilder->createNamedParameter($organization->getId())))
+            ->andWhere('is_siteroot=1')
+            ->execute();
+        if ($statement->rowCount() > 0) {
+            throw new \RuntimeException("A website with this organization's id already exists: " . $organization->getName());
+        }
+
+        // ** Create the new website
+
+        // start transactions
+        $tables = ['be_users', 'pages', 'sys_domain', 'sys_template', 'tt_content'];
+        foreach ($tables as $table) {
+            $this->cnnPool->getConnectionForTable($table)->beginTransaction();
+        }
+
+        $pages = [];
+
+        try {
+            // Create the site pages:
+            // > Root page
+            $rootUid = $this->insertRootPage($organization);
+
+            // > 'Accueil' shortcut
+            $this->insertPage(
+                $organization,
+                $rootUid,
+                'Accueil',
+                '/accueil',
+                '',
+                [
+                    'dokType' => self::DOK_SHORTCUT,
+                    'shortcut' => $rootUid
+                ]
+            );
+
+            // > 'Présentation' page
+            $this->insertPage(
+                $organization,
+                $rootUid,
+                'Présentation',
+                '/presentation'
+            );
+
+            // > 'Présentation > Qui sommes nous?' page (hidden by default)
+            $this->insertPage(
+                $organization,
+                $this->createdPagesIndex['/presentation'],
+                'Qui sommes nous?',
+                '/qui-sommes-nous',
+                '',
+                ['hidden' => 1]
+            );
+
+            // > 'Présentation > Les adhérents' page
+            $this->insertPage(
+                $organization,
+                $this->createdPagesIndex['/presentation'],
+                'Les adhérents',
+                '/les-adherents',
+                self::TEMPLATE_MEMBERS
+            );
+
+            // > 'Présentation > Les membres du CA' page
+            $this->insertPage(
+                $organization,
+                $this->createdPagesIndex['/presentation'],
+                'Les membres du CA',
+                '/les-membres-du-ca',
+                self::TEMPLATE_MEMBERSCA
+            );
+
+            // > 'Présentation > Historique' page (hidden by default)
+            $this->insertPage(
+                $organization,
+                $this->createdPagesIndex['/presentation'],
+                'Historique',
+                '/historique',
+                '',
+                ['hidden' => 1]
+            );
+
+            // > 'Actualités' page (hidden by default)
+            $this->insertPage(
+                $organization,
+                $rootUid,
+                'Actualités',
+                '/actualites',
+                self::TEMPLATE_NEWS
+            );
+
+            // > 'Saison en cours' page
+            $this->insertPage(
+                $organization,
+                $rootUid,
+                'Saison en cours',
+                '/saison-en-cours'
+            );
+
+            // > 'Saison en cours > Les évènements' page
+            $this->insertPage(
+                $organization,
+                $this->createdPagesIndex['/saison-en-cours'],
+                'Les évènements',
+                '/les-evenements',
+                self::TEMPLATE_EVENTS
+            );
+
+            // > 'Vie interne' page (restricted, hidden by default)
+            $this->insertPage(
+                $organization,
+                $rootUid,
+                'Vie interne',
+                '/vie-interne',
+                '',
+                [
+                    'hidden' => 1,
+                    'fe_group' => -2
+                ]
+            );
+
+            // > 'Footer' page (not in the menu)
+            $this->insertPage(
+                $organization,
+                $rootUid,
+                'Footer',
+                '/footer',
+                '',
+                [
+                    'dokType' => self::DOK_FOLDER,
+                    'nav_hide' => 1
+                ]
+            );
+
+            // > 'Footer > Contact' page
+            $this->insertPage(
+                $organization,
+                $this->createdPagesIndex['/footer'],
+                'Contact',
+                '/contact',
+                self::TEMPLATE_CONTACT
+            );
+
+            // > 'Footer > Plan du site' page
+            $this->insertPage(
+                $organization,
+                $this->createdPagesIndex['/footer'],
+                'Plan du site',
+                '/plan-du-site'
+            );
+
+            // > 'Footer > Mentions légales' page
+            $this->insertPage(
+                $organization,
+                $this->createdPagesIndex['/footer'],
+                'Mentions légales',
+                '/mentions-legales'
+            );
+
+            // > 'Page introuvable' page (not in the menu, read-only)
+            $this->insertPage(
+                $organization,
+                $rootUid,
+                'Page introuvable',
+                '/page-introuvable',
+                self::TEMPLATE_E404,
+                [
+                    'nav_hide' => 1,
+                    'no_search' => 1
+                ]
+            );
+
+            // Add content to these pages
+
+            // >> root page content
+            $this->insertContent(
+                $rootUid,
+                self::CTYPE_TEXTPIC,
+                '<h1>Bienvenue sur le site de ' . $organization->getName() . '.</h1>',
+                0
+            );
+
+            // >> page 'qui sommes nous?'
+            $this->insertContent(
+                $this->createdPagesIndex['/qui-sommes-nous'],
+                self::CTYPE_TEXT,
+                'Qui sommes nous ...',
+                0
+            );
+
+            // >> page 'historique'
+            $this->insertContent(
+                $this->createdPagesIndex['/historique'],
+                self::CTYPE_TEXT,
+                "Un peu d'histoire ...",
+                0
+            );
+
+            // >> page 'plan du site'
+            $this->insertContent(
+                $this->createdPagesIndex['/plan-du-site'],
+                self::CTYPE_SITEMAP
+            );
+
+            // >> page 'mentions légales'
+            $this->insertContent(
+                $this->createdPagesIndex['/mentions-legales'],
+                self::CTYPE_TEXT,
+                '<p style="margin-bottom: 0cm"><b>Mentions Légales</b></p>',
+                0
+            );
+
+            // Build and update the domain
+            $domain = $organization->getSubDomain() .  '.opentalent.fr';
+            $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_domain');
+            $queryBuilder->insert('sys_domain')
+                ->values([
+                    'pid' => $rootUid,
+                    'domainName' => $domain
+                ])
+                ->execute();
+
+            // Create the template
+
+            $constants = "plugin.tx_ottemplating {" .
+                         "    settings {" .
+                         "        organization {" .
+                         "            id = " . $organization->getId() .
+                         "            name = " . $organization->getName() .
+                         "            is_network = " . (1 ? $organization->getIsNetwork() : 0) .
+                         "            email = " . $organization->getEmail() .
+                         "            logoid = " . explode('/', $organization->getLogo(), -1)[0] .
+                         "            twitter = " . $organization->getTwitter() .
+                         "            facebook = " . $organization->getFacebook() .
+                         "        }" .
+                         "    network {" .
+                         "            logo = " . $organization->getNetworkLogo() .
+                         "            name = CMF" . $organization->getNetworkName() .
+                         "            url = " . $organization->getNetworkUrl() .
+                         "        }" .
+                         "    }" .
+                         "}" .
+
+            $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_template');
+            $queryBuilder->insert('sys_template')
+                ->values([
+                    'pid' => $rootUid,
+                    'title' => $organization->getName(),
+                    'sitetitle' => $organization->getName(),
+                    'root' => 1,
+                    'clear' => 3,
+                    'include_static_file' => 'EXT:fluid_styled_content/Configuration/TypoScript/,EXT:fluid_styled_content/Configuration/TypoScript/Styling/,EXT:ot_widgets/Configuration/TypoScript,EXT:form/Configuration/TypoScript/,EXT:news/Configuration/TypoScript,EXT:ot_templating/Configuration/TypoScript',
+                    'constants' => $constants
+                ])
+                ->execute();
+
+            // Create the site config.yaml file
+            $config = ['base'=> 'https://' . $domain,
+                'baseVariants'=>[],
+                'errorHandling'=>[
+                    ['errorCode'=>'404',
+                     'errorHandler'=>'Page',
+                     'errorContentSource'=>'t3://page?uid=' . $this->createdPagesIndex['/page-introuvable']],
+                    ['errorCode'=>'403',
+                     'errorHandler'=>'Page',
+                     'errorContentSource'=>'t3://page?uid=' . $this->createdPagesIndex['/page-introuvable']]
+                ],
+                '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, 4);
+            
+
+
+            // Create the contact form
+            $contscatForm = "";
+
+            // Create the BE user
+
+
+            // Create the user_upload directory
+
+//            throw new \Exception('not implemented');
+
+
+            // Try to commit the result
+            foreach ($tables as $table) {
+                $commitSuccess = $this->cnnPool->getConnectionForTable($table)->commit();
+                if (!$commitSuccess) {
+                    throw new \RuntimeException('Something went wrong while commiting the result');
+                }
+            }
+            return $rootUid;
+
+        } catch(\Exception $e) {
+            // rollback
+            foreach ($tables as $table) {
+                $this->cnnPool->getConnectionForTable($table)->rollback();
+            }
+            throw $e;
+        }
+    }
+
+    /**
+     * Insert a new row in the 'pages' table of the Typo3 DB
+     *
+     * @param Organization $organization
+     * @param int $pid
+     * @param string $title
+     * @param string $slug
+     * @param string $template
+     * @param array $moreValues
+     * @return string
+     */
+    private function insertPage(Organization $organization,
+                                int $pid,
+                                string $title,
+                                string $slug,
+                                string $template = '',
+                                array $moreValues = []
+                                ) {
+
+        $defaultValues = [
+            'pid' => $pid,
+            'perms_groupid' => 3,
+            'perms_user' => 27,
+            'cruser_id' => 1,
+            'dokType' => self::DOK_PAGE,
+            'title' => $title,
+            'slug' => $slug,
+            'backend_layout' => 'flux__grid',
+            'backend_layout_next_level' => 'flux__grid',
+            'tx_opentalent_structure_id' => $organization->getId()
+        ];
+
+        if ($template) {
+            $defaultValues['tx_fed_page_controller_action'] = $template;
+            $defaultValues['tx_fed_page_controller_action_sub'] = self::TEMPLATE_1COL;
+        }
+
+        $values = array_merge($defaultValues, $moreValues);
+
+        $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages');
+        $queryBuilder->insert('pages')
+            ->values($values)
+            ->execute();
+
+        $uid = $queryBuilder->getConnection()->lastInsertId();
+
+        $this->createdPagesIndex[$slug] = $uid;
+        return $uid;
+    }
+
+    /**
+     * Insert the root page of a new organization's website
+     *
+     * @param Organization $organization
+     * @return string
+     */
+    private function insertRootPage(Organization $organization) {
+        return $this->insertPage(
+            $organization,
+            self::DEFAULT_ROOT_PID,
+            $organization->getName(),
+            '/',
+            self::TEMPLATE_HOME,
+            [
+                'is_siteroot' => 1,
+                'TSconfig' => 'TCAdefaults.pages.tx_opentalent_structure_id =' . $organization->getId(),
+                'tx_opentalent_template' => self::DEFAULT_THEME,
+                'tx_opentalent_template_preferences' => '{"themeColor":"' . self::DEFAULT_COLOR . '","displayCarousel":"1"}'
+            ]
+        );
+    }
+
+    /**
+     * Insert a new row in the 'tt_content' table of the Typo3 DB
+     *
+     * @param int $pid
+     * @param string $cType
+     * @param string $bodyText
+     * @param int $colPos
+     * @param array $moreValues
+     */
+    private function insertContent(int $pid,
+                                   string $cType=self::CTYPE_TEXT,
+                                   string $bodyText = '',
+                                   int $colPos=0,
+                                   array $moreValues = []) {
+        $defaultValues = [
+            'pid' => $pid,
+            'cruser_id' => 1,
+            'CType' => $cType,
+            'colPos' => $colPos,
+            'bodyText' => $bodyText
+        ];
+
+        $values = array_merge($defaultValues, $moreValues);
+
+        $queryBuilder = $this->cnnPool->getQueryBuilderForTable('tt_content');
+        $queryBuilder->insert('tt_content')
+            ->values($values)
+            ->execute();
+    }
+
+}

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

@@ -0,0 +1,12 @@
+<?php
+
+use Opentalent\OtAdmin\Controller\SiteController;
+
+return [
+        // Create a new organization's website
+        'site_create' => [
+            'path' => '/site-create',
+            'target' => SiteController::class . '::createSiteAction',
+            'access' => 'public'
+        ],        
+];

+ 17 - 0
ot_admin/Configuration/Commands.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Commands to be executed by typo3, where the key of the array
+ * is the name of the command (to be called as the first argument after typo3).
+ * Required parameter is the "class" of the command which needs to be a subclass
+ * of Symfony/Console/Command.
+ */
+
+// /!\ WARNING: this way of register commands will be deprecated with Typo3 v10
+// https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/CommandControllers/Index.html#creating-a-new-command-in-extensions
+
+return [
+    'ot:site:create' => [
+        'class' => Opentalent\OtAdmin\Command\CreateOrganizationCommand::class
+    ]
+];
+

+ 14 - 0
ot_admin/Configuration/Services.yaml

@@ -0,0 +1,14 @@
+services:
+  _defaults:
+    autowire: true
+    autoconfigure: true
+    public: false
+
+  Opentalent\OtAdmin\:
+    resource: '../Classes/*'
+
+  Opentalent\OtAdmin\Command\CreateOrganizationCommand:
+    tags:
+      - name: 'ot:site:create'
+        command: 'ot:site:create'
+        schedulable: false

+ 33 - 0
ot_admin/Readme.md

@@ -0,0 +1,33 @@
+# OtAdmin
+
+Admin-commands for managing the Typo3 instance. 
+This extension provides commands available by the API, CLI, or a dedicated admin-only BE module 
+
+|||
+|---|---|
+| Extension key | ot_admin |
+| Vendor | Opentalent |
+| Nom | OtAdmin |
+
+> Warning: this extension depends on the ot_templating extension
+
+## CLI
+
+Pour exécuter une commande depuis la console 
+(dans le cas d'une installation Typo3 9.5, en mode composer, et avec php-fpm7.4) :
+
+    php7.4 /var/www/typo3/vendor/bin/typo3 [command]
+
+Ou, si le script 'cli' a été ajouté lors de l'installation, 
+ce qui devrait être le cas des installations opentalent:
+
+    sh /var/www/typo3/cli [command]
+
+La liste des commandes fournies par cette extension peut-être obtenue avec:
+
+    php7.4 vendor/bin/typo3 list ot
+
+> A propos de l'implémentation de nouvelles commandes: https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/CommandControllers/Index.html
+
+
+

BIN
ot_admin/Resources/Public/Icons/Extension.png


+ 28 - 0
ot_admin/composer.json

@@ -0,0 +1,28 @@
+{
+    "name": "opentalent/ot-admin",
+    "type": "typo3-cms-extension",
+    "description": "Admin commands for Typo3",
+    "authors": [
+        {
+            "name": "Olivier Massot",
+            "role": "Developer"
+        }
+    ],
+    "require": {
+        "typo3/cms-core": "^9.5 || ^10.4"
+    },
+    "autoload": {
+        "psr-4": {
+            "Opentalent\\OtAdmin\\": "Classes"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Opentalent\\OtAdmin\\Tests\\": "Tests"
+        }
+    },
+    "replace": {
+        "ot_admin": "self.version",
+        "typo3-ter/ot-admin": "self.version"
+    }
+}

+ 29 - 0
ot_admin/ext_emconf.php

@@ -0,0 +1,29 @@
+<?php
+
+/***************************************************************
+ * Extension Manager/Repository config file for ext: "ot_admin"
+ *
+ * Manual updates:
+ * Only the data in the array - anything else is removed by next write.
+ * "version" and "dependencies" must not be touched!
+ ***************************************************************/
+
+$EM_CONF[$_EXTKEY] = [
+    'title' => 'Admin',
+    'description' => 'Admin commands for Typo3',
+    'category' => 'services',
+    'author' => 'Olivier Massot',
+    'author_email' => 'olivier.massot@2iopenservice.fr',
+    'state' => 'alpha',
+    'uploadfolder' => 0,
+    'createDirs' => '',
+    'clearCacheOnLoad' => 0,
+    'version' => '0.1.0',
+    'constraints' => [
+        'depends' => [
+            'typo3' => '8.7.0-10.4.99',
+        ],
+        'conflicts' => [],
+        'suggests' => [],
+    ],
+];

+ 3 - 0
ot_admin/ext_localconf.php

@@ -0,0 +1,3 @@
+<?php
+defined('TYPO3_MODE') || die();
+