SiteController.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. <?php
  2. namespace Opentalent\OtAdmin\Controller;
  3. use Opentalent\OtTemplating\Domain\Model\Organization;
  4. use Opentalent\OtTemplating\Domain\Repository\OrganizationRepository;
  5. use Opentalent\OtTemplating\Exception\ApiRequestException;
  6. use Psr\Log\LoggerAwareInterface;
  7. use Symfony\Component\Yaml\Yaml;
  8. use TYPO3\CMS\Core\Database\ConnectionPool;
  9. use TYPO3\CMS\Core\Utility\GeneralUtility;
  10. use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
  11. use TYPO3\CMS\Extbase\Object\ObjectManager;
  12. /**
  13. * The SiteController implements some admin-only operations
  14. * on Typo3 websites, like creation or update.
  15. */
  16. class SiteController extends ActionController
  17. {
  18. // Templates names
  19. const TEMPLATE_HOME = "OpenTalent.OtTemplating->home";
  20. const TEMPLATE_1COL = "OpenTalent.OtTemplating->1Col";
  21. const TEMPLATE_3COL = "OpenTalent.OtTemplating->home";
  22. const TEMPLATE_EVENTS = "OpenTalent.OtTemplating->events";
  23. const TEMPLATE_STRUCTURESEVENTS = "OpenTalent.OtTemplating->structuresEvents";
  24. const TEMPLATE_STRUCTURES = "OpenTalent.OtTemplating->structures";
  25. const TEMPLATE_CONTACT = "OpenTalent.OtTemplating->contact";
  26. const TEMPLATE_NEWS = "OpenTalent.OtTemplating->news";
  27. const TEMPLATE_MEMBERS = "OpenTalent.OtTemplating->members";
  28. const TEMPLATE_MEMBERSCA = "OpenTalent.OtTemplating->membersCa";
  29. const TEMPLATE_E404 = "OpenTalent.OtTemplating->e404";
  30. // Pages dokType values
  31. const DOK_PAGE = 1;
  32. const DOK_SHORTCUT = 4;
  33. const DOK_FOLDER = 116;
  34. // Contents CTypes
  35. const CTYPE_TEXT = 'text';
  36. const CTYPE_IMAGE = 'image';
  37. const CTYPE_TEXTPIC = 'textpic';
  38. const CTYPE_TEXTMEDIA = 'textmedia';
  39. const CTYPE_HTML = 'html';
  40. const CTYPE_HEADER = 'header';
  41. const CTYPE_UPLOADS = 'uploads';
  42. const CTYPE_LIST = 'list';
  43. const CTYPE_SITEMAP = 'menu_sitemap';
  44. // const DEFAULT_ROOT_PID = 134833;
  45. const DEFAULT_ROOT_PID = 11;
  46. // Default values
  47. const DEFAULT_THEME = 'Classic';
  48. const DEFAULT_COLOR = 'light-blue';
  49. /**
  50. * Doctrine connection pool
  51. * @var object|LoggerAwareInterface|\TYPO3\CMS\Core\SingletonInterface
  52. */
  53. private $cnnPool;
  54. /**
  55. * Index of the pages created
  56. * >> [slug => uid]
  57. * @var array
  58. */
  59. private $createdPagesIndex;
  60. public function __construct()
  61. {
  62. parent::__construct();
  63. $this->cnnPool = GeneralUtility::makeInstance(ConnectionPool::class);
  64. $this->createdPagesIndex = [];
  65. }
  66. /**
  67. * Creates a new website for the given organization, and
  68. * returns the root page uid of the newly created site
  69. *
  70. * @param int $organizationId
  71. * @return int Uid of the root page of the newly created website
  72. * @throws \RuntimeException
  73. */
  74. public function createSiteAction(int $organizationId) {
  75. // ** Get the organization object from the Opentalent API
  76. $manager = GeneralUtility::makeInstance(ObjectManager::class);
  77. $organizationRepository = GeneralUtility::makeInstance(
  78. OrganizationRepository::class,
  79. $manager
  80. );
  81. try {
  82. $organization = $organizationRepository->findById($organizationId);
  83. } catch (ApiRequestException $e) {
  84. throw new \RuntimeException('Unable to fetch the organization with id: ' . $organizationId);
  85. }
  86. // ** Test the existence of a website with this name and or organization id
  87. // Is there a site with this organization's name?
  88. $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages');
  89. $queryBuilder
  90. ->select('uid')
  91. ->from('pages')
  92. ->where($queryBuilder->expr()->eq('title', $queryBuilder->createNamedParameter($organization->getName())))
  93. ->andWhere('is_siteroot=1');
  94. $statement = $queryBuilder->execute();
  95. if ($statement->rowCount() > 0) {
  96. throw new \RuntimeException('A website with this name already exists: ' . $organization->getName());
  97. }
  98. // Is there a site with this organization's id?
  99. $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages');
  100. $statement = $queryBuilder
  101. ->select('uid')
  102. ->from('pages')
  103. ->where($queryBuilder->expr()->eq('tx_opentalent_structure_id', $queryBuilder->createNamedParameter($organization->getId())))
  104. ->andWhere('is_siteroot=1')
  105. ->execute();
  106. if ($statement->rowCount() > 0) {
  107. throw new \RuntimeException("A website with this organization's id already exists: " . $organization->getName());
  108. }
  109. // ** Create the new website
  110. // start transactions
  111. $tables = ['be_users', 'pages', 'sys_domain', 'sys_template', 'tt_content'];
  112. foreach ($tables as $table) {
  113. $this->cnnPool->getConnectionForTable($table)->beginTransaction();
  114. }
  115. try {
  116. // Create the site pages:
  117. // > Root page
  118. $rootUid = $this->insertRootPage($organization);
  119. // > 'Accueil' shortcut
  120. $this->insertPage(
  121. $organization,
  122. $rootUid,
  123. 'Accueil',
  124. '/accueil',
  125. '',
  126. [
  127. 'dokType' => self::DOK_SHORTCUT,
  128. 'shortcut' => $rootUid
  129. ]
  130. );
  131. // > 'Présentation' page
  132. $this->insertPage(
  133. $organization,
  134. $rootUid,
  135. 'Présentation',
  136. '/presentation'
  137. );
  138. // > 'Présentation > Qui sommes nous?' page (hidden by default)
  139. $this->insertPage(
  140. $organization,
  141. $this->createdPagesIndex['/presentation'],
  142. 'Qui sommes nous?',
  143. '/qui-sommes-nous',
  144. '',
  145. ['hidden' => 1]
  146. );
  147. // > 'Présentation > Les adhérents' page
  148. $this->insertPage(
  149. $organization,
  150. $this->createdPagesIndex['/presentation'],
  151. 'Les adhérents',
  152. '/les-adherents',
  153. self::TEMPLATE_MEMBERS
  154. );
  155. // > 'Présentation > Les membres du CA' page
  156. $this->insertPage(
  157. $organization,
  158. $this->createdPagesIndex['/presentation'],
  159. 'Les membres du CA',
  160. '/les-membres-du-ca',
  161. self::TEMPLATE_MEMBERSCA
  162. );
  163. // > 'Présentation > Historique' page (hidden by default)
  164. $this->insertPage(
  165. $organization,
  166. $this->createdPagesIndex['/presentation'],
  167. 'Historique',
  168. '/historique',
  169. '',
  170. ['hidden' => 1]
  171. );
  172. // > 'Actualités' page (hidden by default)
  173. $this->insertPage(
  174. $organization,
  175. $rootUid,
  176. 'Actualités',
  177. '/actualites',
  178. self::TEMPLATE_NEWS
  179. );
  180. // > 'Saison en cours' page
  181. $this->insertPage(
  182. $organization,
  183. $rootUid,
  184. 'Saison en cours',
  185. '/saison-en-cours'
  186. );
  187. // > 'Saison en cours > Les évènements' page
  188. $this->insertPage(
  189. $organization,
  190. $this->createdPagesIndex['/saison-en-cours'],
  191. 'Les évènements',
  192. '/les-evenements',
  193. self::TEMPLATE_EVENTS
  194. );
  195. // > 'Vie interne' page (restricted, hidden by default)
  196. $this->insertPage(
  197. $organization,
  198. $rootUid,
  199. 'Vie interne',
  200. '/vie-interne',
  201. '',
  202. [
  203. 'hidden' => 1,
  204. 'fe_group' => -2
  205. ]
  206. );
  207. // > 'Footer' page (not in the menu)
  208. $this->insertPage(
  209. $organization,
  210. $rootUid,
  211. 'Footer',
  212. '/footer',
  213. '',
  214. [
  215. 'dokType' => self::DOK_FOLDER,
  216. 'nav_hide' => 1
  217. ]
  218. );
  219. // > 'Footer > Contact' page
  220. $this->insertPage(
  221. $organization,
  222. $this->createdPagesIndex['/footer'],
  223. 'Contact',
  224. '/contact',
  225. self::TEMPLATE_CONTACT
  226. );
  227. // > 'Footer > Plan du site' page
  228. $this->insertPage(
  229. $organization,
  230. $this->createdPagesIndex['/footer'],
  231. 'Plan du site',
  232. '/plan-du-site'
  233. );
  234. // > 'Footer > Mentions légales' page
  235. $this->insertPage(
  236. $organization,
  237. $this->createdPagesIndex['/footer'],
  238. 'Mentions légales',
  239. '/mentions-legales'
  240. );
  241. // > 'Page introuvable' page (not in the menu, read-only)
  242. $this->insertPage(
  243. $organization,
  244. $rootUid,
  245. 'Page introuvable',
  246. '/page-introuvable',
  247. self::TEMPLATE_E404,
  248. [
  249. 'nav_hide' => 1,
  250. 'no_search' => 1
  251. ]
  252. );
  253. // Add content to these pages
  254. // >> root page content
  255. $this->insertContent(
  256. $rootUid,
  257. self::CTYPE_TEXTPIC,
  258. '<h1>Bienvenue sur le site de ' . $organization->getName() . '.</h1>',
  259. 0
  260. );
  261. // >> page 'qui sommes nous?'
  262. $this->insertContent(
  263. $this->createdPagesIndex['/qui-sommes-nous'],
  264. self::CTYPE_TEXT,
  265. 'Qui sommes nous ...',
  266. 0
  267. );
  268. // >> page 'historique'
  269. $this->insertContent(
  270. $this->createdPagesIndex['/historique'],
  271. self::CTYPE_TEXT,
  272. "Un peu d'histoire ...",
  273. 0
  274. );
  275. // >> page 'plan du site'
  276. $this->insertContent(
  277. $this->createdPagesIndex['/plan-du-site'],
  278. self::CTYPE_SITEMAP
  279. );
  280. // >> page 'mentions légales'
  281. $this->insertContent(
  282. $this->createdPagesIndex['/mentions-legales'],
  283. self::CTYPE_TEXT,
  284. '<p style="margin-bottom: 0cm"><b>Mentions Légales</b></p>',
  285. 0
  286. );
  287. // Build and update the domain
  288. $domain = $organization->getSubDomain() . '.opentalent.fr';
  289. $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_domain');
  290. $queryBuilder->insert('sys_domain')
  291. ->values([
  292. 'pid' => $rootUid,
  293. 'domainName' => $domain
  294. ])
  295. ->execute();
  296. // Create the template
  297. $constants = "plugin.tx_ottemplating {" .
  298. " settings {" .
  299. " organization {" .
  300. " id = " . $organization->getId() .
  301. " name = " . $organization->getName() .
  302. " is_network = " . (1 ? $organization->getIsNetwork() : 0) .
  303. " email = " . $organization->getEmail() .
  304. " logoid = " . explode('/', $organization->getLogo(), -1)[0] .
  305. " twitter = " . $organization->getTwitter() .
  306. " facebook = " . $organization->getFacebook() .
  307. " }" .
  308. " network {" .
  309. " logo = " . $organization->getNetworkLogo() .
  310. " name = CMF" . $organization->getNetworkName() .
  311. " url = " . $organization->getNetworkUrl() .
  312. " }" .
  313. " }" .
  314. "}";
  315. $queryBuilder = $this->cnnPool->getQueryBuilderForTable('sys_template');
  316. $queryBuilder->insert('sys_template')
  317. ->values([
  318. 'pid' => $rootUid,
  319. 'title' => $organization->getName(),
  320. 'sitetitle' => $organization->getName(),
  321. 'root' => 1,
  322. 'clear' => 3,
  323. 'config' => "config = 'config.frontend_editing = 1\nconfig.absRefPrefix = /',",
  324. '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',
  325. 'constants' => $constants
  326. ])
  327. ->execute();
  328. // Create the site config.yaml file
  329. $config = ['base'=> 'https://' . $domain,
  330. 'baseVariants'=>[],
  331. 'errorHandling'=>[
  332. ['errorCode'=>'404',
  333. 'errorHandler'=>'Page',
  334. 'errorContentSource'=>'t3://page?uid=' . $this->createdPagesIndex['/page-introuvable']],
  335. ['errorCode'=>'403',
  336. 'errorHandler'=>'Page',
  337. 'errorContentSource'=>'t3://page?uid=' . $this->createdPagesIndex['/page-introuvable']]
  338. ],
  339. 'flux_content_types'=>'',
  340. 'flux_page_templates'=>'',
  341. 'languages'=>[[
  342. 'title'=>'Fr',
  343. 'enabled'=>True,
  344. 'base'=>'/',
  345. 'typo3Language'=>'fr',
  346. 'locale'=>'fr_FR',
  347. 'iso-639-1'=>'fr',
  348. 'navigationTitle'=>'Fr',
  349. 'hreflang'=>'fr-FR',
  350. 'direction'=>'ltr',
  351. 'flag'=>'fr',
  352. 'languageId'=>'0',
  353. ]],
  354. 'rootPageId'=>$rootUid,
  355. 'routes'=>[]
  356. ];
  357. $yamlConfig = Yaml::dump($config, 4);
  358. // Create the contact form
  359. $contactForm = "";
  360. // Create the BE user
  361. // Create the user_upload directory (sys_filemounts)
  362. // Try to commit the result
  363. foreach ($tables as $table) {
  364. $commitSuccess = $this->cnnPool->getConnectionForTable($table)->commit();
  365. if (!$commitSuccess) {
  366. throw new \RuntimeException('Something went wrong while commiting the result');
  367. }
  368. }
  369. return $rootUid;
  370. } catch(\Exception $e) {
  371. // rollback
  372. foreach ($tables as $table) {
  373. $this->cnnPool->getConnectionForTable($table)->rollback();
  374. }
  375. throw $e;
  376. }
  377. }
  378. /**
  379. * Insert a new row in the 'pages' table of the Typo3 DB
  380. *
  381. * @param Organization $organization
  382. * @param int $pid
  383. * @param string $title
  384. * @param string $slug
  385. * @param string $template
  386. * @param array $moreValues
  387. * @return string
  388. */
  389. private function insertPage(Organization $organization,
  390. int $pid,
  391. string $title,
  392. string $slug,
  393. string $template = '',
  394. array $moreValues = []
  395. ) {
  396. $defaultValues = [
  397. 'pid' => $pid,
  398. 'perms_groupid' => 3,
  399. 'perms_user' => 27,
  400. 'cruser_id' => 1,
  401. 'dokType' => self::DOK_PAGE,
  402. 'title' => $title,
  403. 'slug' => $slug,
  404. 'backend_layout' => 'flux__grid',
  405. 'backend_layout_next_level' => 'flux__grid',
  406. 'tx_opentalent_structure_id' => $organization->getId()
  407. ];
  408. if ($template) {
  409. $defaultValues['tx_fed_page_controller_action'] = $template;
  410. $defaultValues['tx_fed_page_controller_action_sub'] = self::TEMPLATE_1COL;
  411. }
  412. $values = array_merge($defaultValues, $moreValues);
  413. $queryBuilder = $this->cnnPool->getQueryBuilderForTable('pages');
  414. $queryBuilder->insert('pages')
  415. ->values($values)
  416. ->execute();
  417. $uid = $queryBuilder->getConnection()->lastInsertId();
  418. $this->createdPagesIndex[$slug] = $uid;
  419. return $uid;
  420. }
  421. /**
  422. * Insert the root page of a new organization's website
  423. *
  424. * @param Organization $organization
  425. * @return string
  426. */
  427. private function insertRootPage(Organization $organization) {
  428. return $this->insertPage(
  429. $organization,
  430. self::DEFAULT_ROOT_PID,
  431. $organization->getName(),
  432. '/',
  433. self::TEMPLATE_HOME,
  434. [
  435. 'is_siteroot' => 1,
  436. 'TSconfig' => 'TCAdefaults.pages.tx_opentalent_structure_id =' . $organization->getId(),
  437. 'tx_opentalent_template' => self::DEFAULT_THEME,
  438. 'tx_opentalent_template_preferences' => '{"themeColor":"' . self::DEFAULT_COLOR . '","displayCarousel":"1"}'
  439. ]
  440. );
  441. }
  442. /**
  443. * Insert a new row in the 'tt_content' table of the Typo3 DB
  444. *
  445. * @param int $pid
  446. * @param string $cType
  447. * @param string $bodyText
  448. * @param int $colPos
  449. * @param array $moreValues
  450. */
  451. private function insertContent(int $pid,
  452. string $cType=self::CTYPE_TEXT,
  453. string $bodyText = '',
  454. int $colPos=0,
  455. array $moreValues = []) {
  456. $defaultValues = [
  457. 'pid' => $pid,
  458. 'cruser_id' => 1,
  459. 'CType' => $cType,
  460. 'colPos' => $colPos,
  461. 'bodyText' => $bodyText
  462. ];
  463. $values = array_merge($defaultValues, $moreValues);
  464. $queryBuilder = $this->cnnPool->getQueryBuilderForTable('tt_content');
  465. $queryBuilder->insert('tt_content')
  466. ->values($values)
  467. ->execute();
  468. }
  469. }