Browse Source

resolve conflicts

Olivier Massot 9 months ago
parent
commit
02ba4ae397

+ 1 - 0
config/opentalent/products.yaml

@@ -18,6 +18,7 @@ parameters:
           - LicenceCmfOrganizationER
           - UploadRequest
           - SubdomainAvailability
+          - UserSearchItem
         roles:
           - ROLE_IMPORT
           - ROLE_TAGG

+ 26 - 0
sql/schema-extensions/003-view_search_user.sql

@@ -0,0 +1,26 @@
+CREATE OR REPLACE VIEW view_search_user AS
+    SELECT
+        a.id,
+        a.organization_id as organizationId,
+        p.id AS personId,
+        p.username,
+        p.name,
+        p.givenName,
+        CASE
+            WHEN p.givenName IS NOT NULL AND p.name IS NOT NULL
+                AND p.givenName <> '' AND p.name <> ''
+                THEN CONCAT(p.name, ' ', p.givenName)
+            WHEN p.givenName IS NOT NULL AND p.givenName <> ''
+                THEN p.givenName
+            WHEN p.name IS NOT NULL AND p.name <> ''
+                THEN p.name
+            ELSE ''
+        END AS fullName
+    FROM
+        opentalent.Access a
+            INNER JOIN
+        opentalent.Person p
+        ON
+            a.person_id = p.id
+    WHERE adminAccess IS FALSE;
+

+ 1 - 0
src/Doctrine/Access/CurrentAccessExtension.php

@@ -7,6 +7,7 @@ namespace App\Doctrine\Access;
 use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
+use App\Entity\Custom\Search\UserSearchItem;
 use App\Service\ServiceIterator\CurrentAccessExtensionIterator;
 use Doctrine\ORM\QueryBuilder;
 use Symfony\Bundle\SecurityBundle\Security;

+ 43 - 0
src/Doctrine/Custom/Search/RestrictToOrganizationIdExtension.php

@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Doctrine\Custom\Search;
+
+use ApiPlatform\Metadata\Operation;
+use App\Doctrine\AbstractExtension;
+use App\Entity\Access\Access;
+use App\Entity\Custom\Search\UserSearchItem;
+use App\Service\ServiceIterator\CurrentAccessExtensionIterator;
+use Doctrine\ORM\QueryBuilder;
+use Symfony\Bundle\SecurityBundle\Security;
+
+/**
+ * Filtre de sécurité pour les recherches
+ * Restreint les résultats à ceux de l'organization actuelle.
+ */
+final class RestrictToOrganizationIdExtension extends AbstractExtension
+{
+    public function __construct(
+        private readonly Security $security,
+    ) {}
+
+    public function supports(string $resourceClass, ?Operation $operation): bool
+    {
+        return $resourceClass === UserSearchItem::class;
+    }
+
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
+        /** @var Access $currentUser */
+        $currentUser = $this->security->getUser();
+        if ($currentUser === null || $currentUser->getOrganization() === null) {
+            return;
+        }
+        $rootAlias = $queryBuilder->getRootAliases()[0];
+        $queryBuilder
+            ->andWhere(sprintf('%s.organizationId = :organizationId', $rootAlias))
+            ->setParameter('organizationId', $currentUser->getOrganization()->getId())
+        ;
+    }
+}

+ 142 - 0
src/Entity/Custom/Search/UserSearchItem.php

@@ -0,0 +1,142 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Custom\Search;
+
+use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
+use ApiPlatform\Metadata\ApiFilter;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use App\Filter\ApiPlatform\Utils\InFilter;
+use App\Repository\Custom\Search\UserSearchItemRepository;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Données réduites d'identification d'un utilisateur (ids, noms)
+ * Utilisées entre autres pour les listes déroulantes de recherche.
+ *
+ * Fichier source de la view : ./sql/schema-extensions/003-view_search_user.sql
+ *
+ * @see App\Doctrine\Custom\Search\RestrictToOrganizationIdExtension.php
+ */
+#[ApiResource(
+    operations: [
+        new Get(
+            uriTemplate: '/search/users/{id}',
+            security: 'object.getOrganizationId() == user.getOrganization().getId()'
+        ),
+        new GetCollection(
+            uriTemplate: '/search/users',
+            paginationMaximumItemsPerPage: 10,
+            paginationClientItemsPerPage: true,
+            order: ['fullName' => 'ASC']
+        ),
+    ]
+)]
+#[ORM\Table(name: 'view_search_user')]
+#[ORM\Entity(repositoryClass: UserSearchItemRepository::class, readOnly: true)]
+#[ApiFilter(filterClass: InFilter::class, properties: ['id'])]
+#[ApiFilter(filterClass: SearchFilter::class, properties: ['fullName' => 'ipartial'])]
+class UserSearchItem
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    private int $id;
+
+    #[ORM\Column(type: 'integer')]
+    private ?int $organizationId = null;
+
+    #[ORM\Column(type: 'integer')]
+    private ?int $personId = null;
+
+    #[ORM\Column(type: 'string')]
+    private ?string $username = null;
+
+    #[ORM\Column(type: 'string')]
+    private ?string $name = null;
+
+    #[ORM\Column(type: 'string')]
+    private ?string $givenName = null;
+
+    #[ORM\Column(type: 'string')]
+    private ?string $fullName = null;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+        return $this;
+    }
+
+    public function getOrganizationId(): ?int
+    {
+        return $this->organizationId;
+    }
+
+    public function setOrganizationId(?int $organizationId): self
+    {
+        $this->organizationId = $organizationId;
+        return $this;
+    }
+
+    public function getPersonId(): ?int
+    {
+        return $this->personId;
+    }
+
+    public function setPersonId(?int $personId): self
+    {
+        $this->personId = $personId;
+        return $this;
+    }
+
+    public function getUsername(): ?string
+    {
+        return $this->username;
+    }
+
+    public function setUsername(?string $username): self
+    {
+        $this->username = $username;
+        return $this;
+    }
+
+    public function getName(): ?string
+    {
+        return $this->name;
+    }
+
+    public function setName(?string $name): self
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    public function getGivenName(): ?string
+    {
+        return $this->givenName;
+    }
+
+    public function setGivenName(?string $givenName): self
+    {
+        $this->givenName = $givenName;
+        return $this;
+    }
+
+    public function getFullName(): ?string
+    {
+        return $this->fullName;
+    }
+
+    public function setFullName(?string $fullName): self
+    {
+        $this->fullName = $fullName;
+        return $this;
+    }
+}

+ 2 - 2
src/Entity/Organization/Parameters.php

@@ -69,8 +69,8 @@ class Parameters
     #[ORM\Column(options: ['default' => true])]
     private bool $editCriteriaNotationByAdminOnly = true;
 
-    #[ORM\Column(length: 255, nullable: true)]
-    #[Assert\Regex('/^[a-z0-9]+$/i', message: 'smsSenderName_error')]
+    #[ORM\Column(length: 11, nullable: true)]
+    #[Assert\Regex('/^\w{3,11}$/i', message: 'smsSenderName_error')]
     private ?string $smsSenderName = null;
 
     #[ORM\Column(options: ['default' => false])]

+ 14 - 0
src/Entity/Person/Person.php

@@ -45,6 +45,9 @@ class Person implements UserInterface, PasswordAuthenticatedUserInterface
     #[ORM\Column(options: ['default' => false])]
     private bool $enabled = false;
 
+    #[ORM\Column(name: 'username_canonical', length: 180, unique: true, nullable: true)]
+    private ?string $usernameCanonical = null;
+
     /**
      * @var array<mixed>|null
      */
@@ -147,6 +150,17 @@ class Person implements UserInterface, PasswordAuthenticatedUserInterface
         return (string) $this->username;
     }
 
+    public function getUsernameCanonical(): ?string
+    {
+        return $this->usernameCanonical;
+    }
+
+    public function setUsernameCanonical(?string $usernameCanonical): self
+    {
+        $this->usernameCanonical = $usernameCanonical;
+        return $this;
+    }
+
     public function setUsername(?string $username): self
     {
         $this->username = $username;

+ 0 - 66
src/Filter/ApiPlatform/Utils/FindInSetFilter.php~2.4.1

@@ -1,66 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Filter\ApiPlatform\Utils;
-
-use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
-use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Metadata\Operation;
-use Doctrine\ORM\QueryBuilder;
-use Symfony\Component\PropertyInfo\Type;
-
-final class FindInSetFilter extends AbstractFilter
-{
-    /**
-     * @param mixed[] $context
-     */
-    protected function filterProperty(string $property,
-        mixed $value,
-        QueryBuilder $queryBuilder,
-        QueryNameGeneratorInterface $queryNameGenerator,
-        string $resourceClass,
-        ?Operation $operation = null,
-        array $context = []): void
-    {
-        // otherwise filter is applied to order and page as well
-        if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)
-        ) {
-            return;
-        }
-
-        $alias = $queryBuilder->getRootAliases()[0];
-        $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
-        $queryBuilder
-            ->andWhere(sprintf('find_in_set(:%s, %s.%s) <> 0', $parameterName, $alias, $property))
-            ->setParameter($parameterName, explode(',', $value));
-    }
-
-    /**
-     * API docs.
-     *
-     * @return array<string, mixed[]>
-     */
-    public function getDescription(string $resourceClass): array
-    {
-        if (!$this->properties) {
-            return [];
-        }
-
-        $description = [];
-        foreach ($this->properties as $property => $strategy) {
-            $description["$property"] = [
-                'property' => $property,
-                'type' => Type::BUILTIN_TYPE_STRING,
-                'required' => false,
-                'swagger' => [
-                    'description' => "Filtre de type find_in_set(), vérifie que la valeur est dans le set d'un champs de type CSV",
-                    'name' => 'FindInSet Filter',
-                    'type' => 'Utils Filter',
-                ],
-            ];
-        }
-
-        return $description;
-    }
-}

+ 0 - 66
src/Filter/ApiPlatform/Utils/FindInSetFilter.php~HEAD

@@ -1,66 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Filter\ApiPlatform\Utils;
-
-use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
-use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Metadata\Operation;
-use Doctrine\ORM\QueryBuilder;
-use Symfony\Component\PropertyInfo\Type;
-
-final class FindInSetFilter extends AbstractFilter
-{
-    /**
-     * @param mixed[] $context
-     */
-    protected function filterProperty(string $property,
-        mixed $value,
-        QueryBuilder $queryBuilder,
-        QueryNameGeneratorInterface $queryNameGenerator,
-        string $resourceClass,
-        ?Operation $operation = null,
-        array $context = []): void
-    {
-        // otherwise filter is applied to order and page as well
-        if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)
-        ) {
-            return;
-        }
-
-        $alias = $queryBuilder->getRootAliases()[0];
-        $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
-        $queryBuilder
-            ->andWhere(sprintf('find_in_set(:%s, %s.%s) <> 0', $parameterName, $alias, $property))
-            ->setParameter($parameterName, explode(',', $value));
-    }
-
-    /**
-     * API docs.
-     *
-     * @return array<string, mixed[]>
-     */
-    public function getDescription(string $resourceClass): array
-    {
-        if (!$this->properties) {
-            return [];
-        }
-
-        $description = [];
-        foreach ($this->properties as $property => $strategy) {
-            $description["$property"] = [
-                'property' => $property,
-                'type' => Type::BUILTIN_TYPE_STRING,
-                'required' => false,
-                'swagger' => [
-                    'description' => "Filtre de type find_in_set(), vérifie que la valeur est dans le set d'un champs de type CSV",
-                    'name' => 'FindInSet Filter',
-                    'type' => 'Utils Filter',
-                ],
-            ];
-        }
-
-        return $description;
-    }
-}

+ 0 - 78
src/Filter/ApiPlatform/Utils/InFilter.php~2.4.1

@@ -1,78 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Filter\ApiPlatform\Utils;
-
-use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
-use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Metadata\Operation;
-use Doctrine\ORM\QueryBuilder;
-use Symfony\Component\PropertyInfo\Type;
-
-/**
- * Is property included in the given CSV array.
- *
- * Ex:
- *
- *    /api/accesses?id[in]=123,124,125
- */
-final class InFilter extends AbstractFilter
-{
-    /**
-     * @param mixed[] $context
-     */
-    protected function filterProperty(string $property,
-        mixed $value,
-        QueryBuilder $queryBuilder,
-        QueryNameGeneratorInterface $queryNameGenerator,
-        string $resourceClass,
-        ?Operation $operation = null,
-        array $context = []): void
-    {
-        // otherwise filter is applied to order and page as well
-        if (
-            !$this->isPropertyEnabled($property, $resourceClass)
-            || !$this->isPropertyMapped($property, $resourceClass)
-        ) {
-            return;
-        }
-
-        if (!isset($value['in'])) {
-            return;
-        }
-
-        $alias = $queryBuilder->getRootAliases()[0];
-        $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
-        $queryBuilder
-            ->andWhere(sprintf('%s.%s IN (%s)', $alias, $property, $value['in']));
-    }
-
-    /**
-     * API docs.
-     *
-     * @return array<string, mixed[]>
-     */
-    public function getDescription(string $resourceClass): array
-    {
-        if (!$this->properties) {
-            return [];
-        }
-
-        $description = [];
-        foreach ($this->properties as $property => $strategy) {
-            $description[$property.'[in]'] = [
-                'property' => $property,
-                'type' => Type::BUILTIN_TYPE_STRING,
-                'required' => false,
-                'swagger' => [
-                    'description' => 'Filtre permettant d\'utiliser les IN. (usage: `id[in]=1,2,3`)',
-                    'name' => 'In Filter',
-                    'type' => 'Utils Filter',
-                ],
-            ];
-        }
-
-        return $description;
-    }
-}

+ 0 - 78
src/Filter/ApiPlatform/Utils/InFilter.php~HEAD

@@ -1,78 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Filter\ApiPlatform\Utils;
-
-use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
-use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Metadata\Operation;
-use Doctrine\ORM\QueryBuilder;
-use Symfony\Component\PropertyInfo\Type;
-
-/**
- * Is property included in the given CSV array.
- *
- * Ex:
- *
- *    /api/accesses?id[in]=123,124,125
- */
-final class InFilter extends AbstractFilter
-{
-    /**
-     * @param mixed[] $context
-     */
-    protected function filterProperty(string $property,
-        mixed $value,
-        QueryBuilder $queryBuilder,
-        QueryNameGeneratorInterface $queryNameGenerator,
-        string $resourceClass,
-        ?Operation $operation = null,
-        array $context = []): void
-    {
-        // otherwise filter is applied to order and page as well
-        if (
-            !$this->isPropertyEnabled($property, $resourceClass)
-            || !$this->isPropertyMapped($property, $resourceClass)
-        ) {
-            return;
-        }
-
-        if (!isset($value['in'])) {
-            return;
-        }
-
-        $alias = $queryBuilder->getRootAliases()[0];
-        $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
-        $queryBuilder
-            ->andWhere(sprintf('%s.%s IN (%s)', $alias, $property, $value['in']));
-    }
-
-    /**
-     * API docs.
-     *
-     * @return array<string, mixed[]>
-     */
-    public function getDescription(string $resourceClass): array
-    {
-        if (!$this->properties) {
-            return [];
-        }
-
-        $description = [];
-        foreach ($this->properties as $property => $strategy) {
-            $description[$property.'[in]'] = [
-                'property' => $property,
-                'type' => Type::BUILTIN_TYPE_STRING,
-                'required' => false,
-                'swagger' => [
-                    'description' => 'Filtre permettant d\'utiliser les IN. (usage: `id[in]=1,2,3`)',
-                    'name' => 'In Filter',
-                    'type' => 'Utils Filter',
-                ],
-            ];
-        }
-
-        return $description;
-    }
-}

+ 23 - 0
src/Repository/Custom/Search/UserSearchItemRepository.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Repository\Custom\Search;
+
+use App\Entity\Custom\Search\UserSearchItem;
+use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\Persistence\ManagerRegistry;
+
+/**
+ * @method UserSearchItem|null find($id, $lockMode = null, $lockVersion = null)
+ * @method UserSearchItem|null findOneBy(array $criteria, array $orderBy = null)
+ * @method UserSearchItem[]    findAll()
+ * @method UserSearchItem[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
+ */
+final class UserSearchItemRepository extends ServiceEntityRepository
+{
+    public function __construct(ManagerRegistry $registry)
+    {
+        parent::__construct($registry, UserSearchItem::class);
+    }
+}

+ 4 - 1
src/Service/Typo3/SubdomainService.php

@@ -205,7 +205,10 @@ class SubdomainService
     {
         $adminAccess = $this->accessRepository->findAdminAccess($subdomain->getOrganization());
 
-        $adminAccess->getPerson()->setUsername('admin'.$subdomain->getSubdomain());
+        $person = $adminAccess->getPerson();
+        $newUsername = 'admin'.$subdomain->getSubdomain();
+        $person->setUsername($newUsername);
+        $person->setUsernameCanonical($newUsername);
         $this->em->flush();
     }