applyScope($queryBuilder, $queryNameGenerator, $resourceClass); } public function applyToItem( QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = [], ): void { $this->applyScope($queryBuilder, $queryNameGenerator, $resourceClass); } /** * Applique le filtre de partage de site si les conditions d'application * sont remplies. No-op sinon. */ private function applyScope( QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ): void { // 1) Cette extension cible uniquement la resource User. if (User::class !== $resourceClass) { return; } // 2) Admin ou bypass explicite : visibilite totale. if ($this->security->isGranted('sites.bypass_scope')) { return; } // 3) Pas d'user authentifie -> no-op (API Platform gere le 401 en amont). $user = $this->security->getUser(); if (!$user instanceof User) { return; } $rootAlias = $queryBuilder->getRootAliases()[0]; $callerSiteIds = $user->getSites()->map(fn (Site $s) => $s->getId())->toArray(); // 4) Appelant sans site : comportement defensif -> il ne voit que lui-meme. if (empty($callerSiteIds)) { $queryBuilder ->andWhere(sprintf('%s.id = :self', $rootAlias)) ->setParameter('self', $user->getId()) ; return; } // 5) Cas normal : garder uniquement les users qui partagent au moins // un site avec l'appelant. JOIN sur la relation ManyToMany `.sites` // + filtre IN + DISTINCT pour eviter les lignes dupliquees. $param = $queryNameGenerator->generateParameterName('callerSites'); $queryBuilder ->innerJoin(sprintf('%s.sites', $rootAlias), 's_scope') ->andWhere(sprintf('s_scope.id IN (:%s)', $param)) ->setParameter($param, $callerSiteIds) ->distinct() ; } }