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'; // Default values const DEFAULT_THEME = 'Classic'; const DEFAULT_COLOR = 'light-blue'; // BE rights CONST PRODUCT_MAPPING = [ "school-standard" => 1, // Association writer basic "artist-standard" => 1, // Association writer basic "school-premium" => 3, // Association writer full "artist-premium" => 3, // Association writer full "manager" => 3, // Association writer full ]; /** * Doctrine connection pool * @var object|LoggerAwareInterface|\TYPO3\CMS\Core\SingletonInterface */ private $cnnPool; /** * Index of the pages created during the process * >> [slug => uid] * @var array */ private $createdPagesIndex; /** * List of the directories created in the process (for rollback purposes) * @var array */ private $createdDirs; /** * List of the files created in the process (for rollback purposes) * @var array */ private $createdFiles; public function __construct() { parent::__construct(); $this->cnnPool = GeneralUtility::makeInstance(ConnectionPool::class); $this->createdPagesIndex = []; $this->createdDirs = []; $this->createdFiles = []; } /** * 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|\Exception */ public function createSiteAction(int $organizationId) { $organization = $this->fetchOrganization($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 $this->cnnPool->getConnectionByName('Default')->beginTransaction(); // keep tracks of the created folders and files to be able to remove them during a rollback 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, '
Mentions Légales
', 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(); // update sys_template $constants = $this->genTemplateConstants($organizationId); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_template'); $queryBuilder->insert('sys_template') ->values([ 'pid' => $rootUid, 'title' => $organization->getName(), 'sitetitle' => $organization->getName(), 'root' => 1, 'clear' => 3, 'config' => "config = 'config.frontend_editing = 1\nconfig.absRefPrefix = /',", 'include_static_file' => 'EXT:fluid_styled_content/Configuration/TypoScript/,EXT:fluid_styled_content/Configuration/TypoScript/Styling/,EXT:form/Configuration/TypoScript/,EXT:news/Configuration/TypoScript,EXT:frontend_editing/Configuration/TypoScript,EXT:frontend_editing/Configuration/TypoScript/FluidStyledContent9,EXT:ot_templating/Configuration/TypoScript', 'constants' => $constants ]) ->execute(); // Create the site config.yaml file $this->writeConfigFile($organizationId, $rootUid, $domain); // Create the BE user // -- BE user will be auto-updated by the ot_connect extension -- // Create the user_upload directory and update the sys_filemounts table $uploadRelPath = "/user_upload/" . $organizationId; $uploadDir = $_ENV['TYPO3_PATH_APP'] . "/public/fileadmin" . $uploadRelPath; if (file_exists($uploadDir)) { throw new \RuntimeException("A directory or file " . $uploadDir . " already exists. Abort."); } $this->mkDir($uploadDir); $this->mkDir($uploadDir . '/images'); $this->mkDir($uploadDir . '/Forms'); // Insert the filemounts points (sys_filemounts) $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_filemounts'); $queryBuilder->insert('sys_filemounts') ->values([ 'title' => 'Documents', 'path' => $uploadRelPath . '/images', 'base' => 1 ]) ->execute(); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_filemounts'); $queryBuilder->insert('sys_filemounts') ->values([ 'title' => 'Forms_' . $organizationId, 'path' => $uploadRelPath . '/Forms', 'base' => 1 ]) ->execute(); // Create the BE User $beUid = $this->createBeUser($organizationId, $rootUid, $domain); // Give the keys of the website to this user (makes him the owner) $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); foreach($this->createdPagesIndex as $slug => $uid) { $queryBuilder ->update('pages') ->where($queryBuilder->expr()->eq('uid', $uid)) ->set('perms_userid', $beUid) ->execute(); } // Try to commit the result $commitSuccess = $this->cnnPool->getConnectionByName('Default')->commit(); if (!$commitSuccess) { throw new \RuntimeException('Something went wrong while commiting the result'); } return $rootUid; } catch(\Exception $e) { // rollback $this->cnnPool->getConnectionByName('Default')->rollback(); // remove created files and dirs foreach (array_reverse($this->createdFiles) as $filename) { unlink($filename); } $this->createdFiles = []; foreach (array_reverse($this->createdDirs) as $dirname) { rmdir($dirname); } $this->createdDirs = []; throw $e; } } /** * Update the `sys_template`.`constants` field of the given * organization's website with the data fetched from the opentalent DB. * * @param int $organizationId * @return int */ public function updateSiteConstantsAction(int $organizationId) { $rootUid = $this->findRootUidFor($organizationId); $constants = $this->genTemplateConstants($organizationId); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_template'); $queryBuilder ->update('sys_template') ->set('constants', $constants) ->where($queryBuilder->expr()->eq('uid', $rootUid)) ->execute(); return $rootUid; } /** * Delete the website with all its pages, contents and related records * * If the hard parameter is false, the records' `deleted` field will be set to true and * the files and directories will be renamed. This kind of 'soft' deletion can be undone. * * Otherwise, if hard is set to true, the records and files will be permanently removed, * with no possibility of undoing anything. In this case, you'll have to confirm your intention * by creating a file in the Typo3 root directory, named 'DEL####' (#### is the organization id) * * @param int $organizationId * @param bool $hard * @return int * @throws \Exception */ public function deleteSiteAction(int $organizationId, bool $hard=false) { $rootUid = $this->findRootUidFor($organizationId); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); $isDeleted = $queryBuilder ->select('deleted') ->from('pages') ->where($queryBuilder->expr()->eq('uid', $rootUid)) ->execute() ->fetchColumn(0) == 1; $confirm_file = $_ENV['TYPO3_PATH_APP'] . "/DEL" . $organizationId; if ($hard && !file_exists($confirm_file)) { throw new \RuntimeException( "You are going to completely delete the website with root uid " . $rootUid . ", and all of its pages, files, contents...etc. If you are sure, create a file named '" . $confirm_file . "', and launch this command again." ); } // start transactions $this->cnnPool->getConnectionByName('Default')->beginTransaction(); // keep track of renamed file for an eventual rollback $renamed = []; try { $repository = new OtPageRepository(); $pages = $repository->getAllSubpagesForPage($rootUid); foreach($pages as $page) { $this->delete('tt_content', 'pid', $page['uid'], $hard); $this->delete('pages', 'uid', $page['uid'], $hard); } $this->delete('tt_content', 'pid', $rootUid, $hard); $this->delete('pages', 'uid', $rootUid, $hard); $this->delete('sys_template', 'pid', $rootUid, $hard); // remove filemounts $this->delete('sys_filemounts', 'path', "'/user_upload/" . $organizationId . "/images'", $hard); $this->delete('sys_filemounts', 'path', "'/user_upload/" . $organizationId . "/Forms'", $hard); $this->delete('be_users', 'db_mountpoints', $rootUid, $hard); // Look up for the config.yaml file of the website $configMainDir = $_ENV['TYPO3_PATH_APP'] . '/config/sites'; $configYamlFile = ""; foreach (glob($configMainDir . '/*', GLOB_ONLYDIR) as $subdir) { if (!$isDeleted) { $yamlFile = $subdir . '/config.yaml'; } else { $yamlFile = $subdir . '/config.yaml.deleted'; } if (is_file($yamlFile)) { $conf = Yaml::parseFile($yamlFile); if ($conf['rootPageId'] == $rootUid) { $configYamlFile = $yamlFile; break; } } } if (!$isDeleted) { $uploadDir = $_ENV['TYPO3_PATH_APP'] . '/public/fileadmin/user_upload/' . $organizationId . '/'; } else { $uploadDir = $_ENV['TYPO3_PATH_APP'] . '/public/fileadmin/user_upload/deleted_' . $organizationId . '/'; } // If hard deletion, verify that upload dirs are empty if ($hard && is_file($uploadDir)) { foreach (scandir($uploadDir) as $subdir) { if (!is_dir($subdir)) { throw new \RuntimeException( 'The directory ' . $uploadDir . ' contains non-directory files' . ', this humble script prefers not to take care of them automatically. Cancel.'); } if (is_readable($subdir) && count(scandir($subdir)) > 0) { throw new \RuntimeException( 'The directory ' . $subdir . ' is not empty, ' . 'this humble script prefers not to take care of them automatically. Cancel.'); } } } // If soft deletion, check that no deleted file or directory exist if (!$hard) { $toRename = []; if (!$hard) { if (is_file($configYamlFile)) { $toRename[$configYamlFile] = $configYamlFile . '.deleted'; } if (is_dir($uploadDir)) { $toRename[$uploadDir] = dirname($uploadDir) . '/deleted_' . basename($uploadDir); } } foreach ($toRename as $initialPath => $newPath) { if (is_file($newPath)) { throw new \RuntimeException( 'A file or directory named ' . $newPath . ' already exists, what happened?. Cancel.'); } } } // Delete or rename files and dirs if ($hard) { if (is_file($configYamlFile)) { unlink($configYamlFile); } if (is_dir(dirname($configYamlFile))) { rmdir(dirname($configYamlFile)); } if (is_dir(dirname($uploadDir . 'images'))) { rmdir($uploadDir . 'images'); } if (is_dir(dirname($uploadDir . 'Forms'))) { rmdir($uploadDir . 'Forms'); } if (is_dir(dirname($uploadDir))) { rmdir($uploadDir); } } else { $renamed = []; foreach ($toRename as $initialPath => $newPath) { rename($initialPath, $newPath); $renamed[$initialPath] = $newPath; } } // Try to commit the result (before any eventual file deletion or renaming) $commitSuccess = $this->cnnPool->getConnectionByName('Default')->commit(); if (!$commitSuccess) { throw new \RuntimeException('Something went wrong while commiting the result'); } return $rootUid; } catch(\Exception $e) { // rollback $this->cnnPool->getConnectionByName('Default')->rollback(); if ($hard) { foreach ($renamed as $initialPath => $newPath) { rename($newPath, $initialPath); } } if (file_exists($confirm_file)) { unlink($confirm_file); } throw $e; } } /** * @param string $table * @param string $whereKey * @param $whereValue * @param int $hard */ private function delete(string $table, string $whereKey, $whereValue, $hard=0) { $queryBuilder = $this->cnnPool->getQueryBuilderForTable($table); if (!$hard) { $queryBuilder ->update($table) ->set('deleted', 1) ->where($queryBuilder->expr()->eq($whereKey, $whereValue)) ->execute(); } else { $queryBuilder ->delete($table) ->where($queryBuilder->expr()->eq($whereKey, $whereValue)) ->execute(); } } public function undeleteSiteAction(int $organizationId) { $rootUid = $this->findRootUidFor($organizationId); // start transactions $this->cnnPool->getConnectionByName('Default')->beginTransaction(); // keep track of renamed file for an eventual rollback $renamed = []; try { $repository = new OtPageRepository(); $pages = $repository->getAllSubpagesForPage($rootUid); foreach($pages as $page) { $queryBuilder = $this->cnnPool->getQueryBuilderForTable('tt_content'); $queryBuilder ->update('tt_content') ->set('deleted', 0) ->where($queryBuilder->expr()->eq('pid', $page['uid'])) ->execute(); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); $queryBuilder ->update('pages') ->set('deleted', 0) ->where($queryBuilder->expr()->eq('uid', $page['uid'])) ->execute(); } $queryBuilder = $this->cnnPool->getQueryBuilderForTable('tt_content'); $queryBuilder ->update('tt_content') ->set('deleted', 0) ->where($queryBuilder->expr()->eq('pid', $rootUid)) ->execute(); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); $queryBuilder ->update('pages') ->set('deleted', 0) ->where($queryBuilder->expr()->eq('uid', $rootUid)) ->execute(); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_template'); $queryBuilder ->update('sys_template') ->set('deleted', 0) ->where($queryBuilder->expr()->eq('pid', $rootUid)) ->execute(); // remove filemounts $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_filemounts'); $queryBuilder ->update('sys_filemounts') ->set('deleted', 0) ->where($queryBuilder->expr()->eq('path', "'/user_upload/" . $organizationId . "/images'")) ->execute(); $queryBuilder ->update('sys_filemounts') ->set('deleted', 0) ->where($queryBuilder->expr()->eq('path', "'/user_upload/" . $organizationId . "/Forms'")) ->execute(); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('be_users'); $queryBuilder ->update('be_users') ->set('deleted', 0) ->where($queryBuilder->expr()->eq('db_mountpoints', $rootUid)) ->execute(); // Look up for the config.yaml file of the website $configMainDir = $_ENV['TYPO3_PATH_APP'] . '/config/sites'; $configYamlFile = ""; foreach (glob($configMainDir . '/*', GLOB_ONLYDIR) as $subdir) { $yamlFile = $subdir . '/config.yaml.deleted'; if (is_file($yamlFile)) { $conf = Yaml::parseFile($yamlFile); if ($conf['rootPageId'] == $rootUid) { $configYamlFile = $yamlFile; break; } } } $uploadDir = $_ENV['TYPO3_PATH_APP'] . '/public/fileadmin/user_upload/deleted_' . $organizationId . '/'; $toRename = []; if (is_file($configYamlFile)) { $toRename[$configYamlFile] = dirname($configYamlFile) . '/config.yaml'; } if (is_dir($uploadDir)) { $toRename[$uploadDir] = dirname($uploadDir) . '/' . $organizationId; } foreach ($toRename as $initialPath => $newPath) { if (is_file($newPath)) { throw new \RuntimeException( 'A file or directory named ' . $newPath . ' already exists, what happened?. Cancel.'); } } $renamed = []; foreach ($toRename as $initialPath => $newPath) { rename($initialPath, $newPath); $renamed[$initialPath] = $newPath; } // Try to commit the result $commitSuccess = $this->cnnPool->getConnectionByName('Default')->commit(); if (!$commitSuccess) { throw new \RuntimeException('Something went wrong while commiting the result'); } return $rootUid; } catch(\Exception $e) { // rollback $this->cnnPool->getConnectionByName('Default')->rollback(); foreach ($renamed as $initialPath => $newPath) { rename($newPath, $initialPath); } throw $e; } } /** * Retrieve the Organization object from the repository and then, * from the Opentalent API * @param $organizationId * @return Organization */ private function fetchOrganization($organizationId) { $manager = GeneralUtility::makeInstance(ObjectManager::class); $organizationRepository = GeneralUtility::makeInstance( OrganizationRepository::class, $manager ); try { return $organizationRepository->findById($organizationId); } catch (ApiRequestException $e) { throw new \RuntimeException('Unable to fetch the organization with id: ' . $organizationId); } } /** * Try to find the root page uid of the organization's website and return it. * * @param $organizationId * @return int */ private function findRootUidFor($organizationId) { $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); $queryBuilder->getRestrictions()->removeAll(); $rootUid = $queryBuilder ->select('uid') ->from('pages') ->where('is_siteroot=1') ->andWhere($queryBuilder->expr()->eq('tx_opentalent_structure_id', $organizationId)) ->execute() ->fetchColumn(0); if ($rootUid > 0) { return $rootUid; } $organization = $this->fetchOrganization($organizationId); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); $rootUid = $queryBuilder ->count('uid') ->from('pages') ->where('is_siteroot=1') ->andWhere($queryBuilder->expr()->eq('title', $queryBuilder->createNamedParameter($organization->getName()))) ->andWhere('tx_opentalent_structure_id=null') ->execute() ->fetchColumn(0); if ($rootUid > 0) { return $rootUid; } throw new \RuntimeException("The website of this organization can not be found"); } /** * Determine which folder-type Typo3 page should contain the new website * CREATES IT if needed, and return its uid * * @return int */ private function getParentFolderUid() { $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); $siteCount = $queryBuilder ->count('uid') ->from('pages') ->where('is_siteroot=1') ->execute() ->fetchColumn(0); $thousand = (int)(($siteCount + 1) / 1000); $folderName = "Web Sites " . (1000 * $thousand) . " - " . ((1000 * $thousand) + 999); $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); $uid = $queryBuilder ->select('uid') ->from('pages') ->where($queryBuilder->expr()->eq('title', $queryBuilder->createNamedParameter($folderName))) ->andWhere('dokType=254') ->execute() ->fetchColumn(0); if ($uid == null) { $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages'); $queryBuilder->insert('pages') ->values([ 'pid' => 0, 'title' => $folderName, 'dokType' => 254, 'sorting' => 11264, 'perms_userid' => 1, 'perms_groupid' => 31, 'perms_group' => 27, ]) ->execute(); $uid = $queryBuilder->getConnection()->lastInsertId(); } return $uid; } /** * 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, $this->getParentFolderUid(), $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(); } /** * Return the content of `sys_template`.`constants` of * the website of the given organization * * @param int $organizationId * @return string */ private function genTemplateConstants(int $organizationId) { $rootUid = null; $cnn = new PDO( "mysql:host=prod-back;dbname=opentalent", 'dbcloner', 'wWZ4hYcrmHLW2mUK' ); $cnn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $cnn->prepare( "SELECT o.id, o.name, o.facebook, o.twitter, o.category, o.logo_id, p.logoDonorsMove FROM opentalent.Organization o INNER JOIN opentalent.Parameters p ON o.parameters_id = p.id WHERE o.id=" . $organizationId . ";" ); $stmt->execute(); $stmt->setFetchMode(PDO::FETCH_ASSOC); $org_data = $stmt->fetch(); $stmt = $cnn->prepare( "SELECT c.email FROM opentalent.ContactPoint c INNER JOIN opentalent.organization_contactpoint o ON o.contactPoint_id = c.id WHERE c.contactType = 'PRINCIPAL' AND o.organization_id = " . $organizationId . ";" ); $stmt->execute(); $stmt->setFetchMode(PDO::FETCH_ASSOC); $contact = $stmt->fetch(); $stmt = $cnn->prepare( "SELECT n.name, n.logo, n.url FROM opentalent.Network n INNER JOIN (opentalent.NetworkOrganization l INNER JOIN opentalent.Organization o ON l.organization_id = o.id) ON l.network_id = n.id WHERE l.endDate is NULL AND o.id=" . $organizationId . ";" ); $stmt->execute(); $stmt->setFetchMode(PDO::FETCH_ASSOC); $network = $stmt->fetch(); return "plugin.tx_ottemplating {\n" . " settings {\n" . " organization {\n" . " id = " . $organizationId . "\n" . " name = " . $org_data['name'] . "\n" . " is_network = " . ($org_data['category'] == 'NETWORK' ? '1' : '0') . "\n" . " email = " . $contact['email'] . "\n" . " logoid = " . $org_data['logo_id'] . "\n" . " twitter = " . $org_data['twitter'] . "\n" . " facebook = " . $org_data['facebook'] . "\n" . " }\n" . " network {\n" . " logo = " . $network['logo'] . "\n" . " name = " . $network['name'] . "\n" . " url = " . $network['url'] . "\n" . " }\n" . " }\n" . "}"; } /** * Create the given directory, give its property to the www-data group and * record it as a newly created dir (for an eventual rollback) * * @param string $dirPath */ private function mkDir(string $dirPath) { mkdir($dirPath); $this->createdDirs[] = $dirPath; chgrp($dirPath, 'www-data'); } /** * Write the given file with content, give its property to the www-data group and * record it as a newly created file (for an eventual rollback) * * @param string $path * @param string $content */ private function writeFile(string $path, string $content) { $f = fopen($path, "w"); try { fwrite($f, $content); $this->createdFiles[] = $path; chgrp($path, 'www-data'); } finally { fclose($f); } } /** * Write the .../sites/.../config.yml file of the given site * * @param int $organizationId * @param int $rootUid * @param string $domain */ private function writeConfigFile(int $organizationId, int $rootUid, string $domain) { $folder_id = explode('.', $domain)[0] . '_' . $organizationId; $config_dir = $_ENV['TYPO3_PATH_APP'] . "/config/sites/" . $folder_id; $config_filename = $config_dir . "/config.yaml"; if (file_exists($config_filename)) { throw new \RuntimeException("A file " . $config_filename . " already exists. Abort."); } $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); if (!file_exists($config_dir)) { $this->mkDir($config_dir); } $this->writeFile($config_filename, $yamlConfig); } /** * Create the BE user for the website * The user shall be already created in the Opentalent DB * * @param int $organizationId * @param int $rootUid * @param string $domain * @return int */ private function createBeUser(int $organizationId, int $rootUid, string $domain) { $cnn = new PDO( "mysql:host=prod-back;dbname=opentalent", 'dbcloner', 'wWZ4hYcrmHLW2mUK' ); $cnn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $cnn->prepare( "SELECT p.username, a.id, s.product FROM opentalent.Person p INNER JOIN Access a ON p.id = a.person_id INNER JOIN Settings s on a.organization_id = s.organization_id where a.organization_id=" . $organizationId . " AND a.adminAccess=1;" ); $stmt->execute(); $stmt->setFetchMode(PDO::FETCH_ASSOC); $user_data = $stmt->fetch(); if (!$user_data) { throw new \RuntimeException('Can not find any user with admin access in the Opentalent DB. Abort.'); } // Since we don't want to store the password in the TYPO3 DB, we store a random string instead $randomStr = (new Random)->generateRandomHexString(20); // get the existing filemounts $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_filemounts'); $queryBuilder ->select('uid') ->from('sys_filemounts') ->where("path LIKE '%user_upload/" . $organizationId . "/%'"); $statement = $queryBuilder->execute(); $rows = $statement->fetchAll(3) ?: []; $files = []; foreach ($rows as $row) { $files[] = $row[0]; } $values = [ 'username' => $user_data['username'], 'password' => $randomStr, 'description' => '[ATTENTION: enregistrement auto-généré, ne pas modifier directement] BE Admin for ' . $domain . ' (id: ' . $user_data['id'] . ')', 'deleted' => 0, 'lang' => 'fr', 'usergroup' => isset(self::PRODUCT_MAPPING[$user_data['product']]) ? self::PRODUCT_MAPPING[$user_data['product']] : 1, 'db_mountpoints' => $rootUid, 'file_mountPoints' => join(',', $files), 'options' => 2, 'file_permissions' => 'readFolder,writeFolder,addFolder,renameFolder,moveFolder,deleteFolder,readFile,writeFile,addFile,renameFile,replaceFile,moveFile,copyFile,deleteFile', 'tx_opentalent_opentalentId' => $user_data['id'], 'tx_opentalent_organizationId' => $organizationId, 'tx_opentalent_generationDate' => date('Y/m/d H:i:s') ]; $queryBuilder = $this->cnnPool->getQueryBuilderForTable('be_users'); $queryBuilder->insert('be_users') ->values($values) ->execute(); $beUid = $queryBuilder->getConnection()->lastInsertId(); // enable frontend editing $BE_USER = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Authentication\\BackendUserAuthentication'); $user = $BE_USER->getRawUserByUid($beUid); $BE_USER->user = $user; $BE_USER->backendSetUC(); $BE_USER->uc['frontend_editing'] = 1; $BE_USER->uc['frontend_editing_overlay'] = 1; $BE_USER->writeUC($BE_USER->uc); return $beUid; } }