Browse Source

Merge branch 'release/0.6.5'

Olivier Massot 3 years ago
parent
commit
56dbd96835

+ 107 - 0
ot_admin/Classes/Command/ClearObsoleteWebsitesSiteCommand.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace Opentalent\OtAdmin\Command;
+
+
+use Opentalent\OtAdmin\Controller\SiteController;
+use Opentalent\OtCore\Exception\NoSuchOrganizationException;
+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\Database\ConnectionPool;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+
+/**
+ * This CLI command loop over the ot_websites and delete the ones whom organization doesn't exist anymore
+ *
+ * @package Opentalent\OtAdmin\Command
+ */
+class ClearObsoleteWebsitesSiteCommand 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:clear-obsoletes-websites")
+            ->setDescription("Delete obsolete websites")
+            ->setHelp(" This CLI command loop over the ot_websites and delete 
+                        the ones whom organization doesn't exist anymore")
+            ->addOption(
+                'preview',
+                'p',
+                InputOption::VALUE_NONE,
+                "Only preview, perform no deletion"
+            );
+    }
+
+    /**
+     * -- This method is expected by Typo3, do not rename ou remove --
+     *
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @return int
+     * @throws NoSuchOrganizationException
+     * @throws \Doctrine\DBAL\ConnectionException
+     * @throws \Doctrine\DBAL\DBALException
+     * @throws \Opentalent\OtCore\Exception\InvalidWebsiteConfigurationException
+     * @throws \Opentalent\OtCore\Exception\NoSuchRecordException
+     * @throws \Opentalent\OtCore\Exception\NoSuchWebsiteException
+     * @throws \TYPO3\CMS\Extbase\Object\Exception
+     * @throws \Throwable
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $preview = $input->getOption('preview');
+
+        $io = new SymfonyStyle($input, $output);
+
+        $siteController = GeneralUtility::makeInstance(ObjectManager::class)->get(SiteController::class);
+
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+        $queryBuilder = $connectionPool->getQueryBuilderForTable('ot_websites');
+        $sites = $queryBuilder
+            ->select('organization_id')
+            ->from('ot_websites')
+            ->where($queryBuilder->expr()->eq('deleted', 0))
+            ->execute()
+            ->fetchAll();
+
+        $io->progressStart(count($sites));
+
+        $n = 0;
+        foreach ($sites as $site) {
+            try {
+                $siteController->fetchOrganization($site['organization_id']);
+            } catch (NoSuchOrganizationException $e) {
+                $n++;
+                if ($preview) {
+                    $io->info('Site ' . $site['uid'] . ' to delete : ' . $site['organization_id'] . ' [' . $site['organization_name'] . ']');
+                    continue;
+                }
+                $siteController->deleteSiteAction($site['organization_id']);
+                $io->info('Website ' . $site['uid'] . ' deleted : ' . $site['organization_id'] . ' [' . $site['organization_name'] . ']');
+            }
+            /** @noinspection DisconnectedForeachInstructionInspection */
+            $io->progressAdvance();
+        }
+        $io->progressFinish();
+
+        if (!$preview) {
+            $io->success(sprintf($n . " websites have been successfully deleted"));
+        } else {
+            $io->success(sprintf("Preview ended -- " . $n . ' websites would be deleted'));
+        }
+
+        return Command::SUCCESS;
+    }
+}

+ 0 - 105
ot_admin/Classes/Command/UpdateRoutingIndexCommand.php

@@ -1,105 +0,0 @@
-<?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\Database\ConnectionPool;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Extbase\Object\ObjectManager;
-
-/**
- * This CLI command updates the routing index for the given website
- *
- * @package Opentalent\OtAdmin\Command
- */
-class UpdateRoutingIndexCommand 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:index")
-            ->setDescription("Update the routes index for the given website(s)")
-            ->setHelp("This CLI command updates the routing index for the given website")
-            ->addOption(
-                'all',
-                null,
-                InputOption::VALUE_NONE,
-                "Update all of the organization websites"
-            )
-            ->addArgument(
-                'organization-id',
-                InputArgument::OPTIONAL,
-                "The organization's id in the opentalent DB"
-            );
-    }
-
-    /**
-     * -- This method is expected by Typo3, do not rename ou remove --
-     *
-     * @param InputInterface $input
-     * @param OutputInterface $output
-     * @return int
-     * @throws \TYPO3\CMS\Extbase\Object\Exception
-     */
-    protected function execute(InputInterface $input, OutputInterface $output)
-    {
-        $org_id = $input->getArgument('organization-id');
-        $all = $input->getOption('all');
-
-        if ($all && $org_id) {
-            throw new \InvalidArgumentException("You can not pass both an organization id and the --all option");
-        }
-        if (!$all && !$org_id) {
-            throw new \InvalidArgumentException("You shall either pass an organization id or use the --all option");
-        }
-
-        $io = new SymfonyStyle($input, $output);
-
-        $siteController = GeneralUtility::makeInstance(ObjectManager::class)->get(SiteController::class);
-
-        if ($all) {
-            $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
-            $queryBuilder = $connectionPool->getQueryBuilderForTable('ot_websites');
-            $sites = $queryBuilder
-                ->select('organization_id')
-                ->from('ot_websites')
-                ->where($queryBuilder->expr()->eq('deleted', 0))
-                ->andWhere($queryBuilder->expr()->gt('organization_id', 0))
-                ->execute()
-                ->fetchAll();
-
-            $io->progressStart(count($sites));
-
-            foreach ($sites as $site) {
-                $org_id = $site['organization_id'];
-                try {
-                    $siteController->updateRoutingIndexAction($org_id);
-                } catch (\Throwable $e) {
-                    $io->error('Organization Id: ' . $org_id . ' - ' . $e->getMessage());
-                }
-                $io->progressAdvance(1);
-            }
-            $io->progressFinish();
-
-            $io->success(sprintf("The routing index has all been fully updated"));
-        } else {
-            $rootUid = $siteController->updateRoutingIndexAction($org_id);
-            $io->success(sprintf("The website with root uid " . $rootUid . " routing index has been updated"));
-        }
-        return 0;
-    }
-}

+ 13 - 8
ot_admin/Classes/Controller/SiteController.php

@@ -171,11 +171,12 @@ class SiteController extends ActionController
      */
     public function getSiteInfosAction(int $organizationId): SiteInfos
     {
-        $website = $this->otWebsiteRepository->getWebsiteByOrganizationId($organizationId);
-        $rootUid = $this->otWebsiteRepository->getWebsiteRootUid($website['uid']);
+        $website = $this->otWebsiteRepository->getWebsiteByOrganizationId($organizationId, false);
+        $rootUid = $this->otWebsiteRepository->getWebsiteRootUid($website['uid'], false);
 
         $organizationExtraData = $this->fetchOrganizationExtraData($organizationId);
 
+        # /!\ because of restrictions in the typo3 repo, this will return an empty array if the page is deleted
         $rootPage = $this->otPageRepository->getPage($rootUid);
 
         $site = new SiteInfos(
@@ -186,7 +187,7 @@ class SiteController extends ActionController
             $website['template_preferences'],
             $website['matomo_id'],
             self::IS_PRODUCT_PREMIUM[$organizationExtraData['admin']['product']] ?? false,
-            (bool)$rootPage['deleted'],
+            !$rootPage || $rootPage['deleted'],
             ($rootPage['hidden'] || $rootPage['fe_group'] < 0),
             null,
             null,
@@ -783,11 +784,15 @@ class SiteController extends ActionController
         if ($redirectToParent) {
             $originDomain = $this->otWebsiteRepository->resolveWebsiteDomain($website);
 
-            $organization = $this->fetchOrganization($organizationId);
+            try {
+                $organization = $this->fetchOrganization($organizationId);
 
-            $targetOrganizationId = $organization->getParentId();
-            $targetOrganizationWebsite = $this->otWebsiteRepository->getWebsiteByOrganizationId($targetOrganizationId);
-            $targetDomain = $this->otWebsiteRepository->resolveWebsiteDomain($targetOrganizationWebsite);
+                $targetOrganizationId = $organization->getParentId();
+                $targetOrganizationWebsite = $this->otWebsiteRepository->getWebsiteByOrganizationId($targetOrganizationId);
+                $targetDomain = $this->otWebsiteRepository->resolveWebsiteDomain($targetOrganizationWebsite);
+            } catch (NoSuchOrganizationException $e) {
+                $targetDomain = 'opentalent.fr';
+            }
         }
 
         // start transactions
@@ -1840,7 +1845,7 @@ class SiteController extends ActionController
      * @return Organization
      * @throws NoSuchOrganizationException
      */
-    private function fetchOrganization($organizationId): object
+    public function fetchOrganization($organizationId): object
     {
         try {
             return $this->organizationRepository->findById($organizationId);

+ 4 - 4
ot_admin/Configuration/Commands.php

@@ -31,9 +31,6 @@ return [
     'ot:site:reset-perms' => [
         'class' => Opentalent\OtAdmin\Command\ResetBeUserPermsCommand::class
     ],
-    'ot:site:index' => [
-        'class' => Opentalent\OtAdmin\Command\UpdateRoutingIndexCommand::class
-    ],
     'ot:site:status' => [
         'class' => Opentalent\OtAdmin\Command\GetSiteStatusCommand::class
     ],
@@ -48,6 +45,9 @@ return [
     ],
     'ot:regen-config-files' => [
         'class' => Opentalent\OtAdmin\Command\RegenConfigFilesCommand::class
-    ]
+    ],
+    'ot:clear-obsoletes-websites' => [
+        'class' => Opentalent\OtAdmin\Command\ClearObsoleteWebsitesSiteCommand::class
+    ],
 ];
 

+ 6 - 1
ot_connect/Classes/Service/OtAuthenticationService.php

@@ -112,7 +112,7 @@ class OtAuthenticationService extends AbstractAuthenticationService
         // Does the user already have a session on the Opentalent API?
         $username = $this->getAuthenticatedUsername();
 
-        if ($username != null && $this->authInfo['loginType'] == 'FE' && $this->login['status'] === 'logout') {
+        if ($username != null && $this->authInfo['loginType'] === 'FE' && $this->login['status'] === 'logout') {
             // This is a logout request
             $this->logout();
             return false;
@@ -140,6 +140,11 @@ class OtAuthenticationService extends AbstractAuthenticationService
             }
         }
 
+        /// At this point, username should be set
+        if ($username === null) {
+            return false;
+        }
+
         // Request the latest data for the user and write it in the Typo3 DB
         //   * The shouldUserBeUpdated() method checks if the user was already
         //   generated in the last minutes, to avoid unnecessary operations *

+ 2 - 2
ot_core/Classes/Website/OtPageRepository.php

@@ -144,7 +144,7 @@ class OtPageRepository
 
         $rootPage = $this->getRootPageFor($pageUid);
         $rootUid = $rootPage['uid'] ?? 0;
-        if (!$rootUid > 0) {
+        if (!($rootUid > 0)) {
             throw new NoSiteSelected();
         }
         return $rootUid;
@@ -170,6 +170,6 @@ class OtPageRepository
 
     public function getPage(int $uid): array
     {
-        return $this->pageRepository->getPage($uid, true);
+        return $this->pageRepository->getPage_noCheck($uid);
     }
 }

+ 1 - 1
ot_core/ext_emconf.php

@@ -18,7 +18,7 @@ $EM_CONF[$_EXTKEY] = [
     'uploadfolder' => 0,
     'createDirs' => '',
     'clearCacheOnLoad' => 0,
-    'version' => '0.6.4',
+    'version' => '0.6.5',
     'constraints' => [
         'depends' => [
             'typo3' => '8.7.0-10.4.99',

+ 8 - 2
ot_templating/Classes/ViewHelpers/Members/GetAllCaViewHelper.php

@@ -139,8 +139,14 @@ class GetAllCaViewHelper extends OtAbstractViewHelper {
                 }
             }
 
-            // Remove empty sections
-            $members = array_filter($membersByMission);
+            // Remove empty sections and translate
+            $members = [];
+            foreach ($membersByMission as $mission => $groupMembers) {
+                if (!$groupMembers) {
+                    continue;
+                }
+                $members[ucfirst($this->translate($mission))] = $groupMembers;
+            }
         }
 
         $variables = [$as => $members];

+ 83 - 47
ot_templating/Classes/ViewHelpers/Members/GetAllViewHelper.php

@@ -78,68 +78,104 @@ class GetAllViewHelper extends OtAbstractViewHelper {
         $organizationId = $this->arguments['organizationId'];
         $groupByInstruments = $this->arguments['groupByInstruments'];
 
-        try {
-            // Get members of the structure
-            $collection = $this->memberRepository->findByOrganizationId($organizationId);
-            $members = $collection->getMembers();
-        } catch (ApiRequestException $e) {
-            OtLogger::error(sprintf('API Error: %s', $e->getMessage()));
-            $members = [];
+        if ($groupByInstruments) {
+            $members = $this->getMembersAsGroups($organizationId);
+        } else {
+            $members = $this->getMembersAsSimpleList($organizationId);
         }
 
-        // Sort by instrument (conductor first), then alphabetically by name
-        usort($members,
-            function($a, $b) {
-                if ($a->getInstrument() == 'CONDUCTOR') {
-                    return -1;
-                } else if ($b->getInstrument() == 'CONDUCTOR') {
-                    return 1;
-                } else if ($a->getInstrument() != $b->getInstrument()) {
-                    return strcmp($a->getInstrument(), $b->getInstrument());
-                } else if ($a->getName() != $b->getName()) {
-                    return strcmp($a->getName(), $b->getName());
-                } else {
-                    return 0;
-                }
-            }
+        $variables = [$as => $members];
+        return $this->renderChildrenWithVariables($variables);
+    }
+
+    /**
+     * Return the organization members as a simple array, sorted by name
+     *
+     * @param int $organizationId
+     * @return array
+     */
+    private function getMembersAsSimpleList(int $organizationId): array
+    {
+        $members = $this->getMembers($organizationId);
+        usort(
+            $members,
+            static function ($m1, $m2) { return strcmp($m1->getName(), $m2->getName()); }
         );
+        return $members;
+    }
 
-        if ($groupByInstruments && !empty($members)) {
-
-            // Instruments to display in first place (next will be sorted alphabetically)
-            $stack1 = ['CONDUCTOR' => []];
-
-            $stack2 = [];
-            foreach ($members as $member) {
-                // If that Instrument exist in stack1: put it in this one
-                if (array_key_exists($member->getInstrument(), $stack1)) {
-                    array_push($stack1[$member->getInstrument()], $member);
-                } else {
-                    // Create the new array if needed in stack2, then put the member in it
-                    if (!array_key_exists($member->getInstrument(), $stack2)) {
-                        $stack2[$member->getInstrument()] = [];
-                    }
-                    array_push($stack2[$member->getInstrument()], $member);
-                }
+    /**
+     * Return the organization members as an array of the form :
+     *   [
+     *      'Instrument 1' => [Member1, Member2, ...],
+     *      'Instrument 2' => [Member3, ...],
+     *   ]
+     *
+     * Instruments keys are translated and sorted alphabetically, except for CONDUCTOR which shall appear first
+     * Group members are sorted by name
+     *
+     * @param int $organizationId
+     * @return array
+     */
+    private function getMembersAsGroups(int $organizationId): array
+    {
+        $members = $this->getMembers($organizationId);
+
+        $stack = ['CONDUCTOR' => []];
+        $conductors = [];
+
+        foreach ($members as $member) {
+            if ($member->getInstrument() === 'CONDUCTOR') {
+                $conductors[] = $member;
+                continue;
             }
 
-            // remove empty instruments in stack 1
-            $stack1 = array_filter($stack1);
+            $instrument = ucfirst($this->translate($member->getInstrument()));
+            if (!array_key_exists($instrument, $stack)) {
+                $stack[$instrument] = [$member];
+                continue;
+            }
+
+            $stack[$instrument][] = $member;
+        }
 
-            // sort by instrument stack2
-            ksort($stack2);
+        // remove empty instruments in stack 1
+        $stack = array_filter($stack);
 
-            $members = array_merge($stack1, $stack2);
+        // sort by instrument stack2
+        ksort($stack);
+
+        // sort groups by name
+        foreach (array_values($stack) as $group) {
+            usort(
+                $group,
+                static function ($m1, $m2) { return strcmp($m1->getName(), $m2->getName()); }
+            );
         }
 
-        $variables = [$as => $members];
-        return $this->renderChildrenWithVariables($variables);
+        // Add the conductors in first pos
+        if ($conductors) {
+            $stack = [ucfirst($this->translate('CONDUCTOR')) => $conductors] + $stack;
+        }
+
+        return $stack;
+    }
+
+    private function getMembers(int $organizationId): array
+    {
+        try {
+            // Get members of the structure
+            return $this->memberRepository->findByOrganizationId($organizationId)->getMembers();
+        } catch (ApiRequestException $e) {
+            OtLogger::error(sprintf('API Error: %s', $e->getMessage()));
+            return [];
+        }
     }
 
     /**
      * @param MemberRepository $memberRepository
      */
-    public function injectMemberRepository(MemberRepository $memberRepository)
+    public function injectMemberRepository(MemberRepository $memberRepository): void
     {
         $this->memberRepository = $memberRepository;
     }

+ 77 - 0
ot_templating/Classes/ViewHelpers/Page/GetFirstWithTemplateViewHelper.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace Opentalent\OtTemplating\ViewHelpers;
+
+
+use Closure;
+use Opentalent\OtCore\Website\OtPageRepository;
+use Opentalent\OtCore\ViewHelpers\OtAbstractViewHelper;
+use Opentalent\OtTemplating\ViewHelpers\RootPage\GetIdViewHelper;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
+
+/**
+ * Returns the uid of the first page with the given template in the current website, or null if none
+ *
+ *     {namespace ot=Opentalent\OtTemplating\ViewHelpers}
+ *
+ *     {ot:page.getFirstWithTemplate(template: 'foo')}
+ *
+ * @package Opentalent\OtTemplating\ViewHelpers
+ */
+class FirstWithTemplateViewHelper extends OtAbstractViewHelper
+{
+
+    /**
+     * -- This method is expected by Fluid --
+     * Declares the viewhelper's parameters
+     */
+    public function initializeArguments()
+    {
+        $this->registerArgument(
+            'template',
+            'string',
+            "The template's name",
+            true
+        );
+    }
+
+    /**
+     * -- This method is expected by Fluid --
+     * Renders the content as html
+     *
+     * @param array $arguments
+     * @param Closure $renderChildrenClosure
+     * @param RenderingContextInterface $renderingContext
+     * @return int|null
+     */
+    public static function renderStatic(
+        array $arguments,
+        Closure $renderChildrenClosure,
+        RenderingContextInterface $renderingContext
+    ) {
+        $rootId = GetIdViewHelper::renderStatic(
+            $arguments,
+            $renderChildrenClosure,
+            $renderingContext
+        );
+
+        $pageRepository = GeneralUtility::makeInstance(ObjectManager::class)->get(OtPageRepository::class);
+
+        $subpages = $pageRepository->getAllSubpagesForPage($rootId);
+
+        $templateName = 'OpenTalent.OtTemplating->' . $arguments['template'];
+
+        foreach ($subpages as $page) {
+            if ($page['tx_fed_page_controller_action'] === $templateName
+                & $page['deleted'] === 0
+                & $page['hidden'] === 0
+            ) {
+                return $page['uid'];
+            }
+        }
+        return null;
+    }
+
+}

+ 3 - 0
ot_templating/Resources/Private/Language/locallang.xlf

@@ -512,6 +512,9 @@
 			<trans-unit id="MEMBER_OF_BOARD_OF_HONOR" xml:space="preserve">
 			    <source>Membre d'Honneur du CA</source>
 			</trans-unit>
+			<trans-unit id="ACTIVE_COOPTED_BOARD_MEMBER" xml:space="preserve">
+			    <source>Membre du bureau coopté</source>
+			</trans-unit>
 			<trans-unit id="HONORARY_MEMBER" xml:space="preserve">
 			    <source>Membre d'Honneur</source>
 			</trans-unit>

+ 1 - 1
ot_templating/Resources/Private/Layouts/Classic/StructureDetails.html

@@ -2,7 +2,7 @@
 {namespace ot=Opentalent\OtTemplating\ViewHelpers}
 
 <f:comment><!-- Special layout for the Members page --></f:comment>
-<f:layout name="Structures" />
+<f:layout name="StructuresDetails" />
 
 <v:asset.script name="classic-iframe-resizer"
                 path="EXT:ot_templating/Resources/Public/assets/Classic/script/iframeResizer.min.js"

+ 6 - 0
ot_templating/Resources/Private/Layouts/Modern/Home.html

@@ -15,6 +15,12 @@
                         <f:comment><!-- render the content --></f:comment>
                         <f:render section="Content" />
 
+                        <f:comment><!-- render the content of the leftcol section (left column) --></f:comment>
+                        <f:render section="Leftcol" />
+
+                        <f:comment><!-- render the content of the rightcol section (right column) --></f:comment>
+                        <f:render section="Rightcol" />
+
                         <f:comment><!-- Render the next events section defined in partial/NextEvents.html--></f:comment>
                         <f:render partial="Modern/NextEvents" arguments="{settings: settings}"/>
                         <f:if condition="{settings.organizationIsNetwork}==0">

+ 1 - 1
ot_templating/Resources/Private/Partials/Classic/MembersList.html

@@ -4,7 +4,7 @@
     <f:then>
         <f:for each="{members}" as="members" key="category">
             <h3>
-                <f:format.case value="{f:translate(key: category)}" mode="upper"/>
+                {category}
             </h3>
             <ul class="ot-members-list">
                 <f:for each="{members}" as="member">

+ 1 - 1
ot_templating/Resources/Private/Partials/Modern/Footer.html

@@ -24,7 +24,7 @@
                             <ul class="text-left text-md-right footer-menu">
 
                                 <li class="list-inline-item brand-free">
-                                    <a href="#"
+                                    <a href="https://opentalent.fr/"
                                        title="Opentalent"
                                        class="reseau-opentalent"
                                        data-toggle="modal"

+ 1 - 1
ot_templating/Resources/Private/Partials/Modern/MembersList.html

@@ -4,7 +4,7 @@
     <f:then>
         <f:for each="{members}" as="members" key="category">
             <h3>
-                <f:format.case value="{f:translate(key: category)}" mode="upper"/>
+                {category}
             </h3>
             <ul class="ot-members-list">
                 <f:for each="{members}" as="member">