Ver código fonte

Merge branch 'feature/refactor_ot_api_calls' into develop

Olivier Massot 4 anos atrás
pai
commit
73bc3047e4

+ 0 - 1
composer.json.txt

@@ -1 +0,0 @@
-@See docker/conf/composer.json

+ 93 - 21
doc/problems_resolutions/cross_domain_auth.md

@@ -16,54 +16,126 @@ Les cookies générés par l'authentif auprès de l'api sont donc invisibles pou
 
 
 Pour tester les solutions, il va falloir simuler un domaine différent.
 Pour tester les solutions, il va falloir simuler un domaine différent.
 
 
+Dans le cas standard, si je me rend à l'adresse <local.admin.opentalent.fr/#/login>, et que je me connecte 
+en tant que opentalent74, lorsque je me rend à l'adresse <local.sub.opentalent.fr/ohcluses>, 
+j'apparais comme connecté.
+
+
 Je créé l'entrée suivante dans mon /etc/hosts:
 Je créé l'entrée suivante dans mon /etc/hosts:
 
 
-    127.0.0.1 local.sub.mydomain.fr
+    127.0.0.1 local.sub.customdomain.fr
+
+Je créé un certificat pour l'adresse local.sub.customdomain.fr
 
 
 Je me connecte au docker nginx-proxy, et je remplace les domaines dans la conf de nginx:
 Je me connecte au docker nginx-proxy, et je remplace les domaines dans la conf de nginx:
 
 
-    sed -i 's/local\.sub\.opentalent\.fr/local.sub.mydomain.fr/g' /etc/nginx/default.conf
+    sed -i 's/local\.sub\.opentalent\.fr/local.sub.customdomain.fr/g' /etc/nginx/conf.d/default.conf
     nginx -s reload
     nginx -s reload
 
 
 Je me rend à l'adresse local.admin.opentalent.fr/#/login
 Je me rend à l'adresse local.admin.opentalent.fr/#/login
 
 
 Je m'authentifie en tant que opentalent74
 Je m'authentifie en tant que opentalent74
 
 
-Je me rend à l'adresse http://local.sub.mydomain.fr/ohcluses
+Je me rend à l'adresse http://local.sub.customdomain.fr/ohcluses
 
 
 Je ne suis pas connecté.
 Je ne suis pas connecté.
 
 
-Je reviens à l'url standard:
 
 
-Je me connecte au docker nginx-proxy, et je remplace les domaines dans la conf de nginx:
+## Solutions envisagées
 
 
-    sed -i 's/local\.sub\.mydomain\.fr/local.sub.opentalent.fr/g' /etc/nginx/default.conf
-    nginx -s reload
+### 1- Envoyer les cookies au site depuis l'api via un controller dédié
 
 
+Lors d'une connexion réussie, l'API enverra une requête POST aux sites ayant des domaines custom et pour lesquelles
+le user a un Access
 
 
+Un controller dédié côté Typo3 (ex: setCookies.php) génèrera ensuite les cookies avec les noms de domaines correspondant.
 
 
-## Solutions envisagées
+Pour tester cette solution, j'ajoute une requête curl dans le AuthenticationSuccessListener:
 
 
-### 
+    http://docker.sub.customdomain.fr/typo3conf/ext/ot_connect/setCookies.php?BEARER=' . $data['token']
 
 
+Côté setCookie.php, le contenu est simplement:
 
 
-Lors d'une connexion réussie, l'API enverra une requête POST aux sites ayant des domaines custom et pour lesquelles
-le user a un Access
+    setcookie('BEARER', $_REQUEST['BEARER'], 0, "/", "customdomain.fr");
 
 
-Un controller dédié côté Typo3 (ex: setCookies.php) génèrera ensuite les cookies avec les noms de domaines correspondant.
+Je teste, je m'assure que:
+
+* le fichier setCookie est bien appelé: oui 
+* la variable `$_REQUEST['BEARER']` est bien définie: oui
+
+Je teste dans mon navigateur:
+
+* je vide mes cookies
+* je me rend à https://local.admin.opentalent.fr/#/login
+* je me connecte en tant que opentalent74
+* je me rend à l'adresse https://local.sub.customdomain.fr/ohcluses
+* je ne suis pas authentifié, et aucun cookie n'apparait.
+
+Raté.
+
+### 2- SetCookie + <img>
+
+Plus de détails sur la solution ici: https://subinsb.com/set-same-cookie-on-different-domains/
+
+Le setcookie.php est de la forme:
+
+    setcookie('BEARER', '123456', 0, "/", "customdomain.fr");
+
+J'ajoute la ligne suivante au front du logiciel:
+
+    <img src="http://local.sub.customdomain.fr/typo3conf/ext/ot_connect/setCookies.php" style="display:none;" />
+
+Je teste de la même façon que pour la solution 1
+
+Le cookie est bien présent: Yes!
+
+
+### 2- SetCookie + <img>
+
+J'ajoute à docker/apps/opentalent-admin-2.0/src/app/config/routing/main.js, ligne 79 :
+
+    setCookie:['getOrganization', 'Restangular', function(getOrganization, Restangular){
+        Restangular.oneUrl('no-x-access-id', 'https://local.sub.customdomain.fr')
+            .withHttpConfig({withCredentials: false})
+            .get()
+            .then(resp => {
+            })
+    }],
+
+Afin de tester plus facilement depuis la page de login du logiciel, je modifie la ligne 14 du fichier 
+docker/apps/opentalent-admin-2.0/src/app/ng-admin-jwt-auth/loginController.js en:
+
+    this.$state.go('switch', { organization_id: response.data.profile.organizationConnected}, {'reload':true, 'inherit':false});
+
+Je relance le `gulp serve`
+
+Le setcookie.php est de la forme:
+
+    setcookie('BEARER', 'azerty', 0, "/", "customdomain.fr");
+
+Je teste de la même façon que pour la solution 1
+
+(...)
+
+Après de nombreuses tentatives et blocages (blocages CORS, variable _POST vide...)
+On décide de laisser tomber cette méthode pour le moment.
+
+
+## Solution retenue et mise en oeuvre
+
+On récupère le champs otherWebsite de la structure
 
 
+Si ce champs ne matche pas la regex `https?:\/\/.*\.opentalent\.fr`
 
 
-Etapes:
+Alors, on insère la ligne:
 
 
-1. Le nom de domaine custom doit être stocké dans le champs Parameters.website:
-* vérifier le contenu actuel
-* reprise des custom_domains dans typo
-* automatiser le tout
-2. Créer un script setCookie dans OtConnect qui recevrait les requêtes post provenant de l'API
-3. Ajouter un hook lors du apiSuccess de l'api pour envoyer la requête post
+    <img src="https://<domain>/typo3conf/ext/ot_connect/setCookies.php?bearer=<bearer>" alt="" style="display:none;" />
 
 
-La requête envoyée à setCookie doit contenir simplement le BEARER du user nouvellement authentifié.
+où:
+* <domain> est le champs otherWebsite de la table Parameters de la structure à laquelle le user est connecté
+* <bearer> est le token bearer du user connecté
 
 
-Le fichier setCookie.fr doit générer un cookie correspondant à ce bearer et le renvoyer
+Le setCookie appelé vérifie que le referer est bien en opentalent.fr. Si oui, et si
+la requête a un paramètre BEARER, alors il créé le cookie correspondant dans le bon domaine.
 
 
 
 

+ 18 - 26
ot_connect/Classes/Service/OtAuthenticationService.php

@@ -2,11 +2,12 @@
 namespace Opentalent\OtConnect\Service;
 namespace Opentalent\OtConnect\Service;
 
 
 use DateTime;
 use DateTime;
-use GuzzleHttp\Client;
 use GuzzleHttp\Cookie\CookieJar;
 use GuzzleHttp\Cookie\CookieJar;
 use GuzzleHttp\Cookie\SetCookie;
 use GuzzleHttp\Cookie\SetCookie;
 use GuzzleHttp\Exception\GuzzleException;
 use GuzzleHttp\Exception\GuzzleException;
 use GuzzleHttp\Exception\RequestException;
 use GuzzleHttp\Exception\RequestException;
+use Opentalent\OtCore\Exception\ApiRequestException;
+use Opentalent\OtCore\Service\OpentalentApiService;
 use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
@@ -19,16 +20,13 @@ use \TYPO3\CMS\Core\Authentication\AbstractAuthenticationService;
  */
  */
 class OtAuthenticationService extends AbstractAuthenticationService
 class OtAuthenticationService extends AbstractAuthenticationService
 {
 {
-
-    CONST DOMAIN = 'https://api.opentalent.fr';
-    CONST API_URI = self::DOMAIN . '/api/';
-    CONST LOGIN_URI = self::API_URI . 'login_check';
-    CONST GET_USER_DATA_URI = self::API_URI . 'user/datafortypo3';
-    CONST ISAUTH_URI = self::API_URI . 'user/isauthenticated';
-    CONST LOGOUT_URI = self::API_URI . 'logout';
+    CONST LOGIN_URI = 'api/login_check';
+    CONST GET_USER_DATA_URI = 'api/user/datafortypo3';
+    CONST ISAUTH_URI = 'api/user/isauthenticated';
+    CONST LOGOUT_URI = 'api/logout';
     CONST GROUP_FE_ALL_UID = 18076;
     CONST GROUP_FE_ALL_UID = 18076;
 
 
-    // Cookies'domain needs to be the same that the api's cookies, or guzzle will ignore them.
+    // Cookies' domain needs to be the same that the api's cookies, or guzzle will ignore them.
     CONST COOKIE_DOMAIN = 'opentalent.fr';
     CONST COOKIE_DOMAIN = 'opentalent.fr';
 
 
     CONST PRODUCT_MAPPING = [
     CONST PRODUCT_MAPPING = [
@@ -70,12 +68,9 @@ class OtAuthenticationService extends AbstractAuthenticationService
     const STATUS_AUTHENTICATION_SUCCESS = 200;
     const STATUS_AUTHENTICATION_SUCCESS = 200;
 
 
     /**
     /**
-     * Guzzle Client
-     *
-     * @see http://docs.guzzlephp.org/en/stable/
-     * @var Client
+     * @var object
      */
      */
-    private Client $client;
+    private object $apiService;
 
 
     /**
     /**
      * Guzzle Cookie Jar
      * Guzzle Cookie Jar
@@ -99,7 +94,7 @@ class OtAuthenticationService extends AbstractAuthenticationService
      */
      */
     public function __construct() {
     public function __construct() {
         $this->jar = new CookieJar;
         $this->jar = new CookieJar;
-        $this->client = new Client(['base_uri' => self::DOMAIN, 'cookies' => $this->jar]);
+        $this->apiService = GeneralUtility::makeInstance(OpentalentApiService::class, null, null, $this->jar);
         $this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
         $this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
     }
     }
 
 
@@ -177,7 +172,7 @@ class OtAuthenticationService extends AbstractAuthenticationService
     {
     {
         $this->fillCookieJar();
         $this->fillCookieJar();
         try {
         try {
-            $response = $this->client->request('GET', self::ISAUTH_URI, ['cookies' => $this->jar]);
+            $response = $this->apiService->get(self::ISAUTH_URI, [], ['cookies' => $this->jar]);
 
 
             if ($response->getStatusCode() != 200) {
             if ($response->getStatusCode() != 200) {
                 return null;
                 return null;
@@ -185,10 +180,9 @@ class OtAuthenticationService extends AbstractAuthenticationService
 
 
             return json_decode((string)$response->getBody());
             return json_decode((string)$response->getBody());
 
 
-        } catch (RequestException $e) {
+        } catch (ApiRequestException $e) {
             return null;
             return null;
         }
         }
-
     }
     }
 
 
     /**
     /**
@@ -221,7 +215,7 @@ class OtAuthenticationService extends AbstractAuthenticationService
     {
     {
 
 
         try {
         try {
-            $response = $this->client->request(
+            $response = $this->apiService->request(
                 'POST',
                 'POST',
                 self::LOGIN_URI,
                 self::LOGIN_URI,
                 ['form_params' => ['_username' => $username, '_password' => $password]]
                 ['form_params' => ['_username' => $username, '_password' => $password]]
@@ -237,7 +231,7 @@ class OtAuthenticationService extends AbstractAuthenticationService
             $this->setCookiesFromApiResponse($response);
             $this->setCookiesFromApiResponse($response);
             return true;
             return true;
 
 
-        } catch (RequestException $e) {
+        } catch (ApiRequestException $e) {
             return false;
             return false;
         }
         }
     }
     }
@@ -353,14 +347,12 @@ class OtAuthenticationService extends AbstractAuthenticationService
      *
      *
      * @return array
      * @return array
      */
      */
-    protected function getUserData(): array
+    protected function getUserData(): ?array
     {
     {
         $this->fillCookieJar();
         $this->fillCookieJar();
         try {
         try {
-            $response = $this->client->request('GET', self::GET_USER_DATA_URI, ['cookies' => $this->jar]);
-        } catch (RequestException $e) {
-            return [];
-        } catch (GuzzleException $e) {
+            $response = $this->apiService->request('GET', self::GET_USER_DATA_URI, [], ['cookies' => $this->jar]);
+        } catch (ApiRequestException $e) {
             return [];
             return [];
         }
         }
         return json_decode($response->getBody(), true);
         return json_decode($response->getBody(), true);
@@ -401,7 +393,7 @@ class OtAuthenticationService extends AbstractAuthenticationService
     public function logout(): bool
     public function logout(): bool
     {
     {
         try {
         try {
-            $response = $this->client->request(
+            $response = $this->apiService->request(
                 'GET',
                 'GET',
                 self::LOGOUT_URI
                 self::LOGOUT_URI
             );
             );

+ 16 - 0
ot_connect/setCookies.php

@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * This script will set cookies for the websites with a custom domain (not in '.opentalent.fr')
+ *
+ * NB: It is voluntarily kept separated from typo3, in order to stay as light as possible,
+ *      because it will be called from the Opentalent front app.
+ */
+
+if (
+    preg_match("/https?:\/\/(.*\.)?opentalent.fr(\/.*)?/", $_SERVER['HTTP_REFERER'])
+    && isset($_REQUEST['BEARER'])
+)
+{
+    setcookie('BEARER', $_REQUEST['BEARER'], 0, "/");
+}

+ 36 - 138
ot_core/Classes/Domain/Repository/BaseApiRepository.php

@@ -5,12 +5,14 @@ namespace Opentalent\OtCore\Domain\Repository;
 use GuzzleHttp\Client;
 use GuzzleHttp\Client;
 use GuzzleHttp\Exception\GuzzleException;
 use GuzzleHttp\Exception\GuzzleException;
 use Opentalent\OtCore\Exception\ApiRequestException;
 use Opentalent\OtCore\Exception\ApiRequestException;
+use Opentalent\OtCore\Service\OpentalentApiService;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use Psr\Log\LoggerAwareTrait;
 use Symfony\Component\Yaml\Exception\ParseException;
 use Symfony\Component\Yaml\Exception\ParseException;
 use Symfony\Component\Yaml\Yaml;
 use Symfony\Component\Yaml\Yaml;
 use TYPO3\CMS\Core\Core\ApplicationContext;
 use TYPO3\CMS\Core\Core\ApplicationContext;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
 use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
 
 
 /**
 /**
@@ -21,117 +23,25 @@ abstract class BaseApiRepository implements LoggerAwareInterface
 {
 {
     use LoggerAwareTrait;
     use LoggerAwareTrait;
 
 
-    const DEFAULT_BASE_URI = 'https://api.opentalent.fr/api/';
     const URI_TRAILING_PART = '';
     const URI_TRAILING_PART = '';
     const HYDRA_TYPE = '';
     const HYDRA_TYPE = '';
-    const HTTP_METHOD = 'GET';
     const DEFAULT_ITEMS_PER_PAGE = 8;
     const DEFAULT_ITEMS_PER_PAGE = 8;
 
 
-    protected string $base_uri = self::DEFAULT_BASE_URI;
-    protected array $variants_uris = [];
-    protected Client $client;
-    protected ApplicationContext $context;
+    protected $apiService;
 
 
     /**
     /**
      * BaseApiRepository constructor.
      * BaseApiRepository constructor.
-     *
-     * @param ObjectManagerInterface $objectManager
-     * @param Client|null $client  [For tests only]
-     * @param ApplicationContext|null $context  [For tests only]
-     */
-    public function __construct(
-        ?Client $client = null,
-        ?ApplicationContext $context = null
-    ) {
-        if ($context === null) {
-            $this->context = \TYPO3\CMS\Core\Core\Environment::getContext();
-        } else {
-            $this->context = $context;
-        }
-
-        $this->loadConf();
-
-        if ($client === null) {
-            $this->client = new Client(['base_uri' => $this->getApiUri()]);
-        } else {
-            $this->client = $client;
-        }
-    }
-
-    private function loadConf() {
-        $conf_path = $_ENV['TYPO3_PATH_ROOT'] . '/typo3conf/ext/ot_core/Configuration/ot_config.yaml';
-        $conf = Yaml::parseFile($conf_path);
-
-        // api_variant_uri: Should we set an alternative uri for the API? (dev and testing only)
-        if ($this->context->isDevelopment() || $this->context->isTesting()) {
-            $this->variants_uris = $conf['api_variant_uri'];
-        }
-    }
-
-    /**
-     * Return the API URI for the current repository
-     *
-     * @param string $trailing_part
-     * @return string
      */
      */
-    protected function getApiUri(string $trailing_part = null): string
-    {
-        $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['VIRTUAL_HOST'];
-
-        if (isset($this->variants_uris[$host])) {
-            $uri = $this->variants_uris[$host];
-        } else {
-            $uri = self::DEFAULT_BASE_URI;
-        }
-
-        $trailing_part = $trailing_part ?? $this::URI_TRAILING_PART;
-        $uri = rtrim($uri, '/') . '/' . ltrim($trailing_part, '/');
-        return $uri;
+    public function __construct($apiService = null) {
+        $this->apiService = $apiService ?? GeneralUtility::makeInstance(OpentalentApiService::class);
     }
     }
 
 
     /**
     /**
      * [FOR TESTS ONLY]
      * [FOR TESTS ONLY]
      * @param Client $client
      * @param Client $client
      */
      */
-    protected function injectClient(Client $client) {
-        $this->client = $client;
-    }
-
-    /**
-     * Send a request to the API and
-     * returns the records as an array (members)
-     *
-     * @param array $params
-     * @param string|null $forceUri
-     * @return ApiPagedCollection
-     * @throws ApiRequestException
-     */
-    protected function getApiRecords(array $params = [], ?string $forceUri = null): ApiPagedCollection
-    {
-        $uri = $forceUri ?? $this->getApiUri();
-
-        $body = $this->getJsonDecoded($uri, $params);
-
-        $page = (int)($params['page'] ?? 1);
-
-        // build up the members
-        $members = [];
-
-        if (is_array($body['hydra:member'])) {
-            foreach ($body['hydra:member'] as $record) {
-                $instance = $this->memberToObject($record);
-                if ($instance != null) {
-                    $members[] = $instance;
-                }
-            }
-        }
-
-        return new ApiPagedCollection(
-            (int)($body['hydra:totalItems'] ?? 0),
-            (int)($body['hydra:itemsPerPage'] ?? 0),
-            $page,
-            $members
-        );
+    protected function injectService(OpentalentApiService $apiService) {
+        $this->apiService = $apiService;
     }
     }
 
 
     /**
     /**
@@ -161,57 +71,45 @@ abstract class BaseApiRepository implements LoggerAwareInterface
 
 
     /**
     /**
      * Send a request to the API and
      * Send a request to the API and
-     * returns the Json response as an array
-     *
-     * @param string $uri
-     * @param array $params
-     * @return array
-     * @throws ApiRequestException
-     */
-    protected function getJsonDecoded(string $uri, $params = []): array
-    {
-        return json_decode($this->getBody($uri, $params),true);
-    }
-
-    /**
-     * Send a request to the API and returns
-     * the response's body as a string
-     *
-     * @param string $uri
-     * @param array $params
-     * @return string
-     * @throws ApiRequestException
-     */
-    protected function getBody(string $uri, $params = [])
-    {
-        return (string)$this->getResponse($uri, $params)->getBody();
-    }
-
-    /**
-     * Send a request to the API and returns
-     * the result as a Response object
+     * returns the records as an array (members)
      *
      *
-     * @param string $uri
      * @param array $params
      * @param array $params
-     * @return ResponseInterface
+     * @param string|null $forceUri
+     * @return ApiPagedCollection
      * @throws ApiRequestException
      * @throws ApiRequestException
      */
      */
-    protected function getResponse(string $uri, $params = []): ResponseInterface
+    protected function getApiRecords(array $params = [], ?string $forceUri = null): ApiPagedCollection
     {
     {
-        $uri = $uri . '?_format=json';
+        $params = array("_format" => "json") + $params;   // _format should be the first param to match unit-tests fixtures
         if(!isset($params['itemsPerPage'])) {
         if(!isset($params['itemsPerPage'])) {
             $params['itemsPerPage'] = (string)self::DEFAULT_ITEMS_PER_PAGE;
             $params['itemsPerPage'] = (string)self::DEFAULT_ITEMS_PER_PAGE;
         }
         }
-        if (!empty($params)) {
-            $uri = $uri . '&' . http_build_query($params);
+
+        $uri = ltrim($this::URI_TRAILING_PART, '/');
+        if ($forceUri !== null) {
+            $uri = trim($forceUri, '/') . '/' . $uri;
         }
         }
-        try {
-            if ($this->context->isDevelopment()) {
-                $this->logger->info('API Call: ' . $uri);
+        $body = $this->apiService->getJsonDecoded($uri, $params);
+
+        $page = (int)($params['page'] ?? 1);
+
+        // build up the members
+        $members = [];
+
+        if (is_array($body['hydra:member'])) {
+            foreach ($body['hydra:member'] as $record) {
+                $instance = $this->memberToObject($record);
+                if ($instance != null) {
+                    $members[] = $instance;
+                }
             }
             }
-            return $this->client->request(static::HTTP_METHOD, $uri);
-        } catch (GuzzleException $e) {
-            throw ApiRequestException::from_exception($e);
         }
         }
+
+        return new ApiPagedCollection(
+            (int)($body['hydra:totalItems'] ?? 0),
+            (int)($body['hydra:itemsPerPage'] ?? 0),
+            $page,
+            $members
+        );
     }
     }
 }
 }

+ 1 - 1
ot_core/Classes/Domain/Repository/DonorRepository.php

@@ -7,7 +7,7 @@ use Opentalent\OtCore\Exception\ApiRequestException;
 
 
 class DonorRepository extends BaseApiRepository
 class DonorRepository extends BaseApiRepository
 {
 {
-    const URI_TRAILING_PART = 'public/donors';
+    const URI_TRAILING_PART = '/api/public/donors';
     const HYDRA_TYPE = 'PortailDonor';
     const HYDRA_TYPE = 'PortailDonor';
 
 
     /**
     /**

+ 1 - 1
ot_core/Classes/Domain/Repository/EventRepository.php

@@ -8,7 +8,7 @@ use Opentalent\OtCore\Exception\ApiRequestException;
 
 
 class EventRepository extends BaseApiRepository
 class EventRepository extends BaseApiRepository
 {
 {
-    const URI_TRAILING_PART = 'public/events';
+    const URI_TRAILING_PART = '/api/public/events';
     const HYDRA_TYPE = 'PortailEvent';
     const HYDRA_TYPE = 'PortailEvent';
 
 
     /**
     /**

+ 9 - 0
ot_core/Classes/Domain/Repository/MemberCaRepository.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace Opentalent\OtCore\Domain\Repository;
+
+class MemberCaRepository extends MemberRepository
+{
+    const URI_TRAILING_PART = '/api/public/members_ca';
+    const HYDRA_TYPE = 'PortailMemberByRole';
+}

+ 4 - 18
ot_core/Classes/Domain/Repository/MemberRepository.php

@@ -9,15 +9,8 @@ use Opentalent\OtCore\Exception\ApiRequestException;
 
 
 class MemberRepository extends BaseApiRepository
 class MemberRepository extends BaseApiRepository
 {
 {
-    const URI_TRAILING_PART = 'public/members';
-    const URI_TRAILING_PART_CA = 'public/members_ca';
+    const URI_TRAILING_PART = '/api/public/members';
     const HYDRA_TYPE = 'PortailMemberBySpeciality';
     const HYDRA_TYPE = 'PortailMemberBySpeciality';
-    const HYDRA_TYPE_CA = 'PortailMemberByRole';
-
-    protected function getApiUriCa(): string
-    {
-        return $this->getApiUri(self::URI_TRAILING_PART_CA);
-    }
 
 
     /**
     /**
      * Get the members of the organization
      * Get the members of the organization
@@ -28,17 +21,11 @@ class MemberRepository extends BaseApiRepository
      * @return ApiPagedCollection              Members
      * @return ApiPagedCollection              Members
      * @throws ApiRequestException
      * @throws ApiRequestException
      */
      */
-    public function findByOrganizationId(int $organizationId,
-                                         $only_ca = false) {
+    public function findByOrganizationId(int $organizationId) {
         $params = [];
         $params = [];
         $params['filter[where][organizationId]'] = $organizationId;
         $params['filter[where][organizationId]'] = $organizationId;
         $params['itemsPerPage'] = '200';
         $params['itemsPerPage'] = '200';
-
-        if ($only_ca) {
-            return $this->getApiRecords($params, $this->getApiUriCa());
-        } else {
-            return $this->getApiRecords($params);
-        }
+        return $this->getApiRecords($params);
     }
     }
 
 
     /**
     /**
@@ -49,8 +36,7 @@ class MemberRepository extends BaseApiRepository
      * @throws \Exception
      * @throws \Exception
      */
      */
     protected function memberToObject(array $record) {
     protected function memberToObject(array $record) {
-        if ($record['@type'] != $this::HYDRA_TYPE &&
-            $record['@type'] != $this::HYDRA_TYPE_CA) {
+        if ($record['@type'] != $this::HYDRA_TYPE) {
             return null;
             return null;
         }
         }
         $member = new Member();
         $member = new Member();

+ 1 - 1
ot_core/Classes/Domain/Repository/OrganizationRepository.php

@@ -8,7 +8,7 @@ use Opentalent\OtCore\Exception\ApiRequestException;
 
 
 class OrganizationRepository extends BaseApiRepository
 class OrganizationRepository extends BaseApiRepository
 {
 {
-    const URI_TRAILING_PART = 'public/organizations';
+    const URI_TRAILING_PART = '/api/public/organizations';
     const HYDRA_TYPE = 'PortailOrganization';
     const HYDRA_TYPE = 'PortailOrganization';
 
 
     /**
     /**

+ 180 - 0
ot_core/Classes/Service/OpentalentApiService.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace Opentalent\OtCore\Service;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+use Opentalent\OtCore\Exception\ApiRequestException;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
+
+class OpentalentApiService implements LoggerAwareInterface
+{
+    use LoggerAwareTrait;
+
+    const DEFAULT_BASE_URI = 'https://api.opentalent.fr';
+    protected array $variants_uris = [
+          "preprod.opentalent.fr" => "https://api.preprod.opentalent.fr",
+          "local.sub.opentalent.fr" => "http://docker.nginx.opentalent.fr",
+          "typo3" => "http://docker.nginx.opentalent.fr"
+    ];
+
+    protected object $client;
+    protected object $context;
+
+    /**
+     * @param ObjectManagerInterface $objectManager
+     * @param object|null $client  [For tests only]
+     * @param object|null $context  [For tests only]
+     */
+    public function __construct(
+        ?object $client = null,
+        ?object $context = null,
+        ?object $cookieJar = null
+    ) {
+        // Get the current context (prod, dev...)
+        if ($context === null) {
+            $this->context = \TYPO3\CMS\Core\Core\Environment::getContext();
+        } else {
+            $this->context = $context;
+        }
+
+        if ($client === null) {
+            $args = ['base_uri' => $this->getApiUri()];
+            if ($cookieJar != null) {
+                $args['cookies'] = $cookieJar;
+            }
+            $this->client = new Client($args);
+        } else {
+            $this->client = $client;
+        }
+    }
+
+    public function getClient() {
+        return $this->client;
+    }
+
+    /**
+     * @param Client $client
+     */
+    public function injectClient(Client $client) {
+        $this->client = $client;
+    }
+
+    /**
+     * Return the API URI for the current repository
+     *
+     * @param string $trailing_part
+     * @return string
+     */
+    protected function getApiUri(string $trailing_part = ""): string
+    {
+        $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['VIRTUAL_HOST'];
+
+        if (isset($this->variants_uris[$host])) {
+            $uri = $this->variants_uris[$host];
+        } else {
+            $uri = self::DEFAULT_BASE_URI;
+        }
+
+        return rtrim($uri, '/') . '/' . ltrim($trailing_part, '/');
+    }
+
+    /**
+     * Send a request to the API and
+     * returns the Json response as an array
+     *
+     * @param string $uri
+     * @param array $params
+     * @return array
+     * @throws ApiRequestException
+     * @throws \JsonException
+     */
+    public function getJsonDecoded(
+        string $uri,
+        array $params = []
+    ): array
+    {
+        $body = $this->getBody($uri, $params);
+        $data = json_decode($body,true);
+        if ($data !== null) {
+            return json_decode($body,true);
+        } else {
+            throw new \JsonException('Response can not be decoded as json: ' . substr($body, 0, 300) . '...');
+        }
+    }
+
+    /**
+     * Send a request to the API and returns
+     * the response's body as a string
+     *
+     * @param string $uri
+     * @param array $params
+     * @return string
+     * @throws ApiRequestException
+     */
+    public function getBody(
+        string $uri,
+        array $params = []
+    ): string
+    {
+        return (string)$this->get($uri, $params)->getBody();
+    }
+
+    /**
+     * Send a GET request to the API and returns
+     * the result as a Response object
+     *
+     * @param string $uri
+     * @param array $params
+     * @return ResponseInterface
+     * @throws ApiRequestException
+     */
+    public function get(
+        string $uri,
+        array $params = [],
+        array $config = []
+    ): ResponseInterface
+    {
+        return $this->request('GET', $uri, $params, $config);
+    }
+
+    /**
+     * Send a request to the API and returns
+     * the result as a Response object
+     *
+     * @param string $httpMethod
+     * @param string $uri
+     * @param array $params
+     * @param array $config
+     * @return ResponseInterface
+     * @throws ApiRequestException
+     */
+    public function request(
+        string $httpMethod,
+        string $uri,
+        array $params = [],
+        array $config = []
+    ): ?ResponseInterface
+    {
+        $parsedUrl = parse_url($uri);
+        $params += ($parsedUrl['query'] ?? []);
+
+        $path = http_build_query($params);
+        $uri = rtrim($uri, '/');
+        if ($path) {
+            $uri .= '?' . $path;
+        }
+
+        try {
+            if ($this->context->isDevelopment()) {
+                $this->logger->info('API Call: ' . $uri);
+            }
+            return $this->client->request($httpMethod, $uri, $config);
+        } catch (GuzzleException $e) {
+            throw ApiRequestException::from_exception($e);
+        }
+    }
+}

+ 13 - 5
ot_core/Tests/Unit/Domain/Repository/AbstractApiRepositoryTestCase.php

@@ -5,7 +5,9 @@ namespace Opentalent\OtCore\Tests\Unit\Domain\Repository;
 use GuzzleHttp\Client;
 use GuzzleHttp\Client;
 use Nimut\TestingFramework\TestCase\UnitTestCase;
 use Nimut\TestingFramework\TestCase\UnitTestCase;
 use Opentalent\OtCore\Domain\Repository\BaseApiRepository;
 use Opentalent\OtCore\Domain\Repository\BaseApiRepository;
+use Opentalent\OtCore\Service\OpentalentApiService;
 use Opentalent\OtCore\Tests\Unit\Fixtures\ApiResponseFixtures;
 use Opentalent\OtCore\Tests\Unit\Fixtures\ApiResponseFixtures;
+use Prophecy\Argument;
 use ReflectionClass;
 use ReflectionClass;
 use TYPO3\CMS\Core\Core\ApplicationContext;
 use TYPO3\CMS\Core\Core\ApplicationContext;
 
 
@@ -32,6 +34,8 @@ abstract class AbstractApiRepositoryTestCase extends UnitTestCase
      */
      */
     protected $client;
     protected $client;
 
 
+    protected $service;
+
     public function setUp() {
     public function setUp() {
         // mock the application context
         // mock the application context
         $this->context = $this->prophesize(ApplicationContext::class);
         $this->context = $this->prophesize(ApplicationContext::class);
@@ -40,29 +44,33 @@ abstract class AbstractApiRepositoryTestCase extends UnitTestCase
         $this->context->isTesting()->willReturn(true);
         $this->context->isTesting()->willReturn(true);
 
 
         $this->client = $this->prophesize(Client::class);
         $this->client = $this->prophesize(Client::class);
+        $this->service = new OpentalentApiService(
+            $this->client->reveal(),
+            $this->context->reveal()
+        );
 
 
         $repositoryClass = new ReflectionClass(static::TESTED_CLASS);
         $repositoryClass = new ReflectionClass(static::TESTED_CLASS);
         foreach ($repositoryClass->getMethods() as $method) {
         foreach ($repositoryClass->getMethods() as $method) {
             $method->setAccessible(true);
             $method->setAccessible(true);
         }
         }
-        $this->repository = $repositoryClass->newInstanceArgs([$this->client->reveal(), $this->context->reveal()]);
+        $this->repository = $repositoryClass->newInstanceArgs([$this->service]);
 
 
         $this->fixture = new ApiResponseFixtures();
         $this->fixture = new ApiResponseFixtures();
     }
     }
 
 
-    protected function injectClientFor($uri) {
+    protected function injectClientFor($uri, $http_method='GET') {
         // mock the Guzzle client
         // mock the Guzzle client
         $willReturn = $this->fixture->get($uri);
         $willReturn = $this->fixture->get($uri);
         $client = $this->prophesize(Client::class);
         $client = $this->prophesize(Client::class);
-        $client->request(BaseApiRepository::HTTP_METHOD, $uri)
+        $client->request($http_method, $uri)
             ->shouldBeCalled()
             ->shouldBeCalled()
             ->willReturn($willReturn);
             ->willReturn($willReturn);
 
 
-        $reflectionObject = new \ReflectionObject($this->repository);
+        $reflectionObject = new \ReflectionObject($this->service);
         $reflectionMethod = $reflectionObject->getMethod('injectClient');
         $reflectionMethod = $reflectionObject->getMethod('injectClient');
         $reflectionMethod->setAccessible(true);
         $reflectionMethod->setAccessible(true);
 
 
-        $reflectionMethod->invokeArgs($this->repository, [$client->reveal()]);
+        $reflectionMethod->invokeArgs($this->service, [$client->reveal()]);
     }
     }
 
 
     protected function callMemberToObject(array $record) {
     protected function callMemberToObject(array $record) {

+ 2 - 110
ot_core/Tests/Unit/Domain/Repository/BaseApiRepositoryTest.php

@@ -13,10 +13,6 @@ use Opentalent\OtCore\Exception\ApiRequestException;
  */
  */
 class ConcreteBaseApiRepository extends BaseApiRepository {
 class ConcreteBaseApiRepository extends BaseApiRepository {
     protected function memberToObject(array $member) { return $member; }
     protected function memberToObject(array $member) { return $member; }
-    public function injectClient($client) { parent::injectClient($client); }
-    public function getResponse($uri, $params = []): \Psr\Http\Message\ResponseInterface { return parent::getResponse($uri, $params); }
-    public function getBody($uri, $params = []): string { return parent::getBody($uri, $params); }
-    public function getJsonDecoded($uri, $params = []): array { return parent::getJsonDecoded($uri, $params); }
     public function getApiFirstRecord($params = [], $forceUri = null) { return parent::getApiFirstRecord($params, $forceUri); }
     public function getApiFirstRecord($params = [], $forceUri = null) { return parent::getApiFirstRecord($params, $forceUri); }
     public function getApiRecords($params = [], $forceUri = null): ApiPagedCollection { return parent::getApiRecords($params, $forceUri); }
     public function getApiRecords($params = [], $forceUri = null): ApiPagedCollection { return parent::getApiRecords($params, $forceUri); }
 }
 }
@@ -25,110 +21,6 @@ class BaseApiRepositoryTest extends AbstractApiRepositoryTestCase
 {
 {
     const TESTED_CLASS = 'Opentalent\OtCore\Tests\Unit\Domain\Repository\ConcreteBaseApiRepository';
     const TESTED_CLASS = 'Opentalent\OtCore\Tests\Unit\Domain\Repository\ConcreteBaseApiRepository';
 
 
-    /**
-     * get should build a valid url, send a query and
-     * return a Guzzle response object
-     *
-     * @test
-     */
-    public function get() {
-
-        $base_uri = "https://api.opentalent.fr/api/public/organizations";
-        $params = ['itemsPerPage' => 10, 'foo' => 1];
-
-        // uri as it is supposed to be processed by the repo
-        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";
-
-        $this->injectClientFor($processed_uri);
-        $actual = $this->repository->getResponse($base_uri, $params);
-
-        $this->assertEquals(200, $actual->getStatusCode());
-    }
-
-    /**
-     * get should build a valid url, send a query and
-     * return a Guzzle response object
-     *
-     * @test
-     */
-    public function getWithNoParams() {
-
-        $base_uri = "https://api.opentalent.fr/api/public/organizations";
-        $params = [];
-
-        // uri as it is supposed to be processed by the repo
-        $processed_uri = $base_uri . "?_format=json&itemsPerPage=8";
-
-        $this->injectClientFor($processed_uri);
-        $actual = $this->repository->getResponse($base_uri, $params);
-
-        $this->assertEquals(200, $actual->getStatusCode());
-    }
-
-    /**
-     * get should build a valid url, send a query and
-     * return a Guzzle response object
-     *
-     * @test
-     */
-    public function getInvalidUri()
-    {
-        $base_uri = "a very bad uri";
-        $params = [];
-        $processed_uri = $base_uri . "?_format=json&itemsPerPage=8";
-
-        $client = $this->prophesize(Client::class);
-        $client->request(BaseApiRepository::HTTP_METHOD, $processed_uri)
-            ->shouldBeCalled()
-            ->willThrow(new \GuzzleHttp\Exception\TransferException('error'));
-        $this->inject($this->repository, "client", $client->reveal());
-
-        try {
-            $this->repository->getResponse($base_uri, $params);
-            throw new \AssertionError("An ApiRequestException should have been thrown");
-        } catch (ApiRequestException $e) {
-            $this->assertEquals('error', $e->getMessage());
-        }
-    }
-
-    /**
-     * getBody should return the response body as a string
-     *
-     * @test
-     */
-    public function getBody() {
-
-        $base_uri = "https://api.opentalent.fr/api/public/organizations";
-        $params = ['itemsPerPage' => 10, 'foo' => 1];
-
-        // uri as it is supposed to be processed by the repo
-        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";
-
-        $this->injectClientFor($processed_uri);
-        $actual = $this->repository->getBody($base_uri, $params);
-
-        $this->assertEquals('{"@context": "/api/contexts/PortailOrganization"}', $actual);
-    }
-
-    /**
-     * getBody should return the response body as an array
-     *
-     * @test
-     */
-    public function getJsonDecoded() {
-
-        $base_uri = "https://api.opentalent.fr/api/public/organizations";
-        $params = ['itemsPerPage' => 10, 'foo' => 1];
-
-        // uri as it is supposed to be processed by the repo
-        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";
-
-        $this->injectClientFor($processed_uri);
-        $actual = $this->repository->getJsonDecoded($base_uri, $params);
-
-        $this->assertEquals(["@context" => "/api/contexts/PortailOrganization"], $actual);
-    }
-
     /**
     /**
      * getApiFirstRecord should return the first member of the api response
      * getApiFirstRecord should return the first member of the api response
      * this member has been processed by the memberToObject method, which does nothing here
      * this member has been processed by the memberToObject method, which does nothing here
@@ -136,7 +28,7 @@ class BaseApiRepositoryTest extends AbstractApiRepositoryTestCase
      * @test
      * @test
      */
      */
     public function getApiFirstRecord() {
     public function getApiFirstRecord() {
-        $base_uri = "https://api.opentalent.fr/api/public/organizations";
+        $base_uri = "api/public/organizations";
         $params = ['filter[where][id]' => 1];
         $params = ['filter[where][id]' => 1];
         $processed_uri = $base_uri . "?_format=json&filter%5Bwhere%5D%5Bid%5D=1&page=1&totalItems=1&itemsPerPage=8";
         $processed_uri = $base_uri . "?_format=json&filter%5Bwhere%5D%5Bid%5D=1&page=1&totalItems=1&itemsPerPage=8";
         $this->injectClientFor($processed_uri);
         $this->injectClientFor($processed_uri);
@@ -153,7 +45,7 @@ class BaseApiRepositoryTest extends AbstractApiRepositoryTestCase
      * @test
      * @test
      */
      */
     public function getApiRecords() {
     public function getApiRecords() {
-        $base_uri = "https://api.opentalent.fr/api/public/organizations";
+        $base_uri = "api/public/organizations";
         $params = ['filter[where][id]' => 1];
         $params = ['filter[where][id]' => 1];
         $processed_uri = $base_uri . "?_format=json&filter%5Bwhere%5D%5Bid%5D=1&itemsPerPage=8";
         $processed_uri = $base_uri . "?_format=json&filter%5Bwhere%5D%5Bid%5D=1&itemsPerPage=8";
         $this->injectClientFor($processed_uri);
         $this->injectClientFor($processed_uri);

+ 2 - 2
ot_core/Tests/Unit/Domain/Repository/DonorRepositoryTest.php

@@ -18,7 +18,7 @@ class DonorRepositoryTest extends AbstractApiRepositoryTestCase
     public function findByOrganizationId() {
     public function findByOrganizationId() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/donors?_format=json&organizationId=1&page=1&itemsPerPage=8";
+        $expected_uri = "api/public/donors?_format=json&organizationId=1&page=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findByOrganizationId($organization_id);
         $actual = $this->repository->findByOrganizationId($organization_id);
@@ -34,7 +34,7 @@ class DonorRepositoryTest extends AbstractApiRepositoryTestCase
     public function findParentsByOrganizationId() {
     public function findParentsByOrganizationId() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/donors?_format=json&organizationId=1&parent=1&page=1&itemsPerPage=8";
+        $expected_uri = "api/public/donors?_format=json&organizationId=1&parent=1&page=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findParentsByOrganizationId($organization_id);
         $actual = $this->repository->findParentsByOrganizationId($organization_id);

+ 8 - 8
ot_core/Tests/Unit/Domain/Repository/EventRepositoryTest.php

@@ -17,7 +17,7 @@ class EventRepositoryTest extends AbstractApiRepositoryTestCase
     public function findById() {
     public function findById() {
         $event_id = 2;
         $event_id = 2;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/events?_format=json&filter%5Bwhere%5D%5Bid%5D=2&page=1&totalItems=1&itemsPerPage=8";
+        $expected_uri = "api/public/events?_format=json&filter%5Bwhere%5D%5Bid%5D=2&page=1&totalItems=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findById($event_id);
         $actual = $this->repository->findById($event_id);
@@ -33,7 +33,7 @@ class EventRepositoryTest extends AbstractApiRepositoryTestCase
     public function findByOrganizationId() {
     public function findByOrganizationId() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&page=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&itemsPerPage=8";
+        $expected_uri = "api/public/events?_format=json&organizationId=1&page=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findByOrganizationId($organization_id);
         $actual = $this->repository->findByOrganizationId($organization_id);
@@ -49,7 +49,7 @@ class EventRepositoryTest extends AbstractApiRepositoryTestCase
     public function findByOrganizationIdWithParams() {
     public function findByOrganizationIdWithParams() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&page=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC";
+        $expected_uri = "api/public/events?_format=json&organizationId=1&page=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findByOrganizationId(
         $actual = $this->repository->findByOrganizationId(
@@ -71,7 +71,7 @@ class EventRepositoryTest extends AbstractApiRepositoryTestCase
     public function findParentsByOrganizationId() {
     public function findParentsByOrganizationId() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&page=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&parent=1&itemsPerPage=8";
+        $expected_uri = "api/public/events?_format=json&organizationId=1&page=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&parent=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findParentsByOrganizationId($organization_id);
         $actual = $this->repository->findParentsByOrganizationId($organization_id);
@@ -87,7 +87,7 @@ class EventRepositoryTest extends AbstractApiRepositoryTestCase
     public function findParentsByOrganizationIdWithParams() {
     public function findParentsByOrganizationIdWithParams() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&page=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&parent=1";
+        $expected_uri = "api/public/events?_format=json&organizationId=1&page=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&parent=1";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findParentsByOrganizationId(
         $actual = $this->repository->findParentsByOrganizationId(
@@ -109,7 +109,7 @@ class EventRepositoryTest extends AbstractApiRepositoryTestCase
     public function findChildrenByOrganizationId() {
     public function findChildrenByOrganizationId() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&children=1&itemsPerPage=8";
+        $expected_uri = "api/public/events?_format=json&organizationId=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&children=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findChildrenByOrganizationId($organization_id);
         $actual = $this->repository->findChildrenByOrganizationId($organization_id);
@@ -125,7 +125,7 @@ class EventRepositoryTest extends AbstractApiRepositoryTestCase
     public function findChildrenByOrganizationIdWithParams() {
     public function findChildrenByOrganizationIdWithParams() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&children=1";
+        $expected_uri = "api/public/events?_format=json&organizationId=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&children=1";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findChildrenByOrganizationId(
         $actual = $this->repository->findChildrenByOrganizationId(
@@ -147,7 +147,7 @@ class EventRepositoryTest extends AbstractApiRepositoryTestCase
     public function searchBy() {
     public function searchBy() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&filter%5Bwhere%5D%5Bid%5D=1&itemsPerPage=8";
+        $expected_uri = "api/public/events?_format=json&organizationId=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&filter%5Bwhere%5D%5Bid%5D=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->searchBy($organization_id, ['filter[where][id]' => 1]);
         $actual = $this->repository->searchBy($organization_id, ['filter[where][id]' => 1]);

+ 1 - 17
ot_core/Tests/Unit/Domain/Repository/MemberRepositoryTest.php

@@ -18,29 +18,13 @@ class MemberRepositoryTest extends AbstractApiRepositoryTestCase
     public function findByOrganizationId() {
     public function findByOrganizationId() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/members?_format=json&filter%5Bwhere%5D%5BorganizationId%5D=1&itemsPerPage=200";
+        $expected_uri = "api/public/members?_format=json&filter%5Bwhere%5D%5BorganizationId%5D=1&itemsPerPage=200";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findByOrganizationId($organization_id);
         $actual = $this->repository->findByOrganizationId($organization_id);
         $this->assertEquals($organization_id, $actual->getMembers()[0]->getOrganizationId());
         $this->assertEquals($organization_id, $actual->getMembers()[0]->getOrganizationId());
     }
     }
 
 
-    /**
-     * findByOrganizationId should return an ApiPagedCollection object containing
-     * the member(s) matching the given organizationId
-     *
-     * @test
-     */
-    public function findByOrganizationIdWithCa() {
-        $organization_id = 1;
-
-        $expected_uri = "https://api.opentalent.fr/api/public/members_ca?_format=json&filter%5Bwhere%5D%5BorganizationId%5D=1&itemsPerPage=200";
-        $this->injectClientFor($expected_uri);
-
-        $actual = $this->repository->findByOrganizationId($organization_id, true);
-        $this->assertEquals($organization_id, $actual->getMembers()[0]->getOrganizationId());
-    }
-
     /**
     /**
      * memberToObject should return null if the given
      * memberToObject should return null if the given
      * member has not the good type
      * member has not the good type

+ 4 - 4
ot_core/Tests/Unit/Domain/Repository/OrganizationRepositoryTest.php

@@ -17,7 +17,7 @@ class OrganizationRepositoryTest extends AbstractApiRepositoryTestCase
     public function findById() {
     public function findById() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/organizations?_format=json&filter%5Bwhere%5D%5Bid%5D=1&page=1&totalItems=1&itemsPerPage=8";
+        $expected_uri = "api/public/organizations?_format=json&filter%5Bwhere%5D%5Bid%5D=1&page=1&totalItems=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findById($organization_id);
         $actual = $this->repository->findById($organization_id);
@@ -32,7 +32,7 @@ class OrganizationRepositoryTest extends AbstractApiRepositoryTestCase
     public function findByName() {
     public function findByName() {
         $name = 'a name';
         $name = 'a name';
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/organizations?_format=json&filter%5Bwhere%5D%5Bname%5D=a+name&page=1&totalItems=1&itemsPerPage=8";
+        $expected_uri = "api/public/organizations?_format=json&filter%5Bwhere%5D%5Bname%5D=a+name&page=1&totalItems=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findByName($name);
         $actual = $this->repository->findByName($name);
@@ -47,7 +47,7 @@ class OrganizationRepositoryTest extends AbstractApiRepositoryTestCase
     public function findByInexistantName() {
     public function findByInexistantName() {
         $name = 'a unknown name';
         $name = 'a unknown name';
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/organizations?_format=json&filter%5Bwhere%5D%5Bname%5D=a+unknown+name&page=1&totalItems=1&itemsPerPage=8";
+        $expected_uri = "api/public/organizations?_format=json&filter%5Bwhere%5D%5Bname%5D=a+unknown+name&page=1&totalItems=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findByName($name);
         $actual = $this->repository->findByName($name);
@@ -62,7 +62,7 @@ class OrganizationRepositoryTest extends AbstractApiRepositoryTestCase
     public function findChildrenById() {
     public function findChildrenById() {
         $organization_id = 1;
         $organization_id = 1;
 
 
-        $expected_uri = "https://api.opentalent.fr/api/public/organizations?_format=json&parentId=1&children=1&page=1&itemsPerPage=8";
+        $expected_uri = "api/public/organizations?_format=json&parentId=1&children=1&page=1&itemsPerPage=8";
         $this->injectClientFor($expected_uri);
         $this->injectClientFor($expected_uri);
 
 
         $actual = $this->repository->findChildrenById($organization_id);
         $actual = $this->repository->findChildrenById($organization_id);

+ 30 - 30
ot_core/Tests/Unit/Fixtures/ApiResponseFixtures.php

@@ -160,39 +160,39 @@ class ApiResponseFixtures
      * @var string[]
      * @var string[]
      */
      */
     private $map = [
     private $map = [
-        'https://api.opentalent.fr/api/public/organizations?_format=json&itemsPerPage=10&foo=1' => 'stub',
-        'https://api.opentalent.fr/api/public/organizations?_format=json&itemsPerPage=8' => 'stub',
-        'https://api.opentalent.fr/api/public/organizations?_format=json&filter%5Bwhere%5D%5Bid%5D=1&page=1&totalItems=1&itemsPerPage=8' => 'org',
-        'https://api.opentalent.fr/api/public/organizations?_format=json&filter%5Bwhere%5D%5Bname%5D=a+name&page=1&totalItems=1&itemsPerPage=8' => 'org',
-        'https://api.opentalent.fr/api/public/organizations?_format=json&parentId=1&children=1&page=1&itemsPerPage=8' => 'org',
-        'https://api.opentalent.fr/api/public/organizations?_format=json&filter%5Bwhere%5D%5Bid%5D=1&itemsPerPage=8' => 'org',
-        'https://api.opentalent.fr/api/public/organizations?_format=json&filter%5Bwhere%5D%5Bname%5D=a+unknown+name&page=1&totalItems=1&itemsPerPage=8' => 'org',
-        'https://api.opentalent.fr/api/public/events?_format=json&filter%5Bwhere%5D%5Bid%5D=2&page=1&totalItems=1&itemsPerPage=8' => 'event',
-        'https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&page=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&itemsPerPage=8' => 'event',
-        'https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&page=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&parent=1&itemsPerPage=8' => 'event',
-        'https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&children=1&itemsPerPage=8' => 'event',
-        'https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&filter%5Bwhere%5D%5Bid%5D=1&itemsPerPage=8' => 'event',
-        'https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&page=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC' => 'event',
-        'https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&page=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&parent=1' => 'event',
-        'https://api.opentalent.fr/api/public/events?_format=json&organizationId=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&children=1' => 'event',
-        'https://api.opentalent.fr/api/public/donors?_format=json&organizationId=1&page=1&itemsPerPage=8' => 'donor',
-        'https://api.opentalent.fr/api/public/donors?_format=json&organizationId=1&parent=1&page=1&itemsPerPage=8' => 'donor',
-        'https://api.opentalent.fr/api/public/members?_format=json&filter%5Bwhere%5D%5BorganizationId%5D=1&itemsPerPage=200' => 'member',
-        'https://api.opentalent.fr/api/public/members_ca?_format=json&filter%5Bwhere%5D%5BorganizationId%5D=1&itemsPerPage=200' => 'member'
+        'api/public/organizations?_format=json' => 'stub',
+        'api/public/organizations?_format=json&itemsPerPage=10&foo=1' => 'stub',
+        'api/public/organizations?_format=json&itemsPerPage=8' => 'stub',
+        'api/public/organizations?_format=json&filter%5Bwhere%5D%5Bid%5D=1&page=1&totalItems=1&itemsPerPage=8' => 'org',
+        'api/public/organizations?_format=json&filter%5Bwhere%5D%5Bname%5D=a+name&page=1&totalItems=1&itemsPerPage=8' => 'org',
+        'api/public/organizations?_format=json&parentId=1&children=1&page=1&itemsPerPage=8' => 'org',
+        'api/public/organizations?_format=json&filter%5Bwhere%5D%5Bid%5D=1&itemsPerPage=8' => 'org',
+        'api/public/organizations?_format=json&filter%5Bwhere%5D%5Bname%5D=a+unknown+name&page=1&totalItems=1&itemsPerPage=8' => 'org',
+        'api/public/events?_format=json&filter%5Bwhere%5D%5Bid%5D=2&page=1&totalItems=1&itemsPerPage=8' => 'event',
+        'api/public/events?_format=json&organizationId=1&page=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&itemsPerPage=8' => 'event',
+        'api/public/events?_format=json&organizationId=1&page=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&parent=1&itemsPerPage=8' => 'event',
+        'api/public/events?_format=json&organizationId=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&children=1&itemsPerPage=8' => 'event',
+        'api/public/events?_format=json&organizationId=1&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&filter%5Bwhere%5D%5Bid%5D=1&itemsPerPage=8' => 'event',
+        'api/public/events?_format=json&organizationId=1&page=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC' => 'event',
+        'api/public/events?_format=json&organizationId=1&page=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&parent=1' => 'event',
+        'api/public/events?_format=json&organizationId=1&itemsPerPage=1&filter%5Bwhere%5D%5BdatetimeStart%5D%5Bgte%5D=2021-01-01T00%3A00%3A00%2B00%3A00&filter%5Bwhere%5D%5BdatetimeEnd%5D%5Blte%5D=2021-01-31T00%3A00%3A00%2B00%3A00&filter%5Border%5D%5B0%5D%5BdatetimeStart%5D=ASC&children=1' => 'event',
+        'api/public/donors?_format=json&organizationId=1&page=1&itemsPerPage=8' => 'donor',
+        'api/public/donors?_format=json&organizationId=1&parent=1&page=1&itemsPerPage=8' => 'donor',
+        'api/public/members?_format=json&filter%5Bwhere%5D%5BorganizationId%5D=1&itemsPerPage=200' => 'member',
+        'api/public/members_ca?_format=json&filter%5Bwhere%5D%5BorganizationId%5D=1&itemsPerPage=200' => 'member'
     ];
     ];
 
 
     public function get($url) {
     public function get($url) {
-        if (array_key_exists($url, $this->map)) {
-            $query = $this->map[$url];
-            $response = new HtmlResponse(
-                $this->responses[$query],
-                200,
-                []
-            );
-            return $response;
-        } else {
-            return null;
+        foreach ($this->map as $needle => $response_key) {
+            if (str_ends_with($url, $needle))
+            {
+                return new HtmlResponse(
+                    $this->responses[$response_key],
+                    200,
+                    []
+                );
+            }
         }
         }
+        throw new \RuntimeException("No fixture result for uri " . $url);
     }
     }
-
 }
 }

+ 163 - 0
ot_core/Tests/Unit/Service/OpentalentApiServiceTest.php

@@ -0,0 +1,163 @@
+<?php
+
+namespace Opentalent\OtCore\Tests\Unit\Service;
+
+use GuzzleHttp\Client;
+use Nimut\TestingFramework\TestCase\UnitTestCase;
+use Opentalent\OtCore\Exception\ApiRequestException;
+use Opentalent\OtCore\Service\OpentalentApiService;
+use Opentalent\OtCore\Tests\Unit\Fixtures\ApiResponseFixtures;
+use PHPUnit\TextUI\RuntimeException;
+use Prophecy\Argument;
+use TYPO3\CMS\Core\Core\ApplicationContext;
+
+class OpentalentApiServiceTest extends UnitTestCase
+{
+    protected $context;
+    /**
+     * @var ApiResponseFixtures
+     */
+    protected $fixture;
+    /**
+     * @var \Prophecy\Prophecy\ObjectProphecy
+     */
+    protected $client;
+
+    protected $service;
+
+    public function setUp() {
+        // mock the application context
+        $this->context = $this->prophesize(ApplicationContext::class);
+        $this->context->isProduction()->willReturn(false);
+        $this->context->isDevelopment()->willReturn(false);
+        $this->context->isTesting()->willReturn(true);
+
+        $this->client = $this->prophesize(Client::class);
+
+        $this->service = new OpentalentApiService($this->client->reveal(), $this->context->reveal());
+
+        $this->fixture = new ApiResponseFixtures();
+    }
+
+    protected function injectClientFor($uri, $http_method='GET') {
+        // mock the Guzzle client
+        $client = $this->prophesize(Client::class);
+        try {
+            $willReturn = $this->fixture->get($uri);
+            $client->request($http_method, $uri)
+                ->shouldBeCalled()
+                ->willReturn($willReturn);
+
+        } catch (\RuntimeException $e) {
+            $client->request($http_method, Argument::type('string'))
+                ->shouldBeCalled()
+                ->willThrow(new \GuzzleHttp\Exception\TransferException('error'));
+        }
+
+        $reflectionObject = new \ReflectionObject($this->service);
+        $reflectionMethod = $reflectionObject->getMethod('injectClient');
+        $reflectionMethod->setAccessible(true);
+
+        $reflectionMethod->invokeArgs($this->service, [$client->reveal()]);
+    }
+
+    /**
+     * get should build a valid url, send a query and
+     * return a Guzzle response object
+     *
+     * @test
+     */
+    public function get() {
+
+        $base_uri = "api/public/organizations";
+        $params = ['itemsPerPage' => 10, 'foo' => 1];
+
+        // uri as it is supposed to be processed by the repo
+        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";
+
+        $this->injectClientFor($processed_uri);
+        $actual = $this->service->request('GET', $base_uri, $params);
+
+        $this->assertEquals(200, $actual->getStatusCode());
+    }
+
+    /**
+     * get should build a valid url, send a query and
+     * return a Guzzle response object
+     *
+     * @test
+     */
+    public function getWithNoParams() {
+
+        $base_uri = "api/public/organizations";
+        $params = [];
+
+        // uri as it is supposed to be processed by the repo
+        $processed_uri = $base_uri . "?_format=json";
+
+        $this->injectClientFor($processed_uri);
+        $actual = $this->service->request('GET', $base_uri, $params);
+
+        $this->assertEquals(200, $actual->getStatusCode());
+    }
+
+    /**
+     * get should build a valid url, send a query and
+     * return a Guzzle response object
+     *
+     * @test
+     */
+    public function getInvalidUri()
+    {
+        $base_uri = "a very bad uri";
+        $params = [];
+
+        $this->injectClientFor($base_uri);
+
+        try {
+            $res = $this->service->request('GET', $base_uri, $params);
+            throw new \AssertionError("An ApiRequestException should have been thrown");
+        } catch (ApiRequestException $e) {
+            $this->assertEquals('error', $e->getMessage());
+        }
+    }
+
+    /**
+     * getBody should return the response body as a string
+     *
+     * @test
+     */
+    public function getBody() {
+
+        $base_uri = "api/public/organizations";
+        $params = ['itemsPerPage' => 10, 'foo' => 1];
+
+        // uri as it is supposed to be processed by the repo
+        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";
+
+        $this->injectClientFor($processed_uri);
+        $actual = $this->service->getBody($base_uri, $params);
+
+        $this->assertEquals('{"@context": "/api/contexts/PortailOrganization"}', $actual);
+    }
+
+    /**
+     * getBody should return the response body as an array
+     *
+     * @test
+     */
+    public function getJsonDecoded() {
+
+        $base_uri = "api/public/organizations";
+        $params = ['itemsPerPage' => 10, 'foo' => 1];
+
+        // uri as it is supposed to be processed by the repo
+        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";
+
+        $this->injectClientFor($processed_uri);
+        $actual = $this->service->getJsonDecoded($base_uri, $params);
+
+        $this->assertEquals(["@context" => "/api/contexts/PortailOrganization"], $actual);
+    }
+
+}

+ 7 - 7
ot_templating/Classes/ViewHelpers/Members/GetAllCaViewHelper.php

@@ -3,8 +3,8 @@
 namespace Opentalent\OtTemplating\ViewHelpers\Members;
 namespace Opentalent\OtTemplating\ViewHelpers\Members;
 
 
 use FluidTYPO3\Vhs\Traits\TemplateVariableViewHelperTrait;
 use FluidTYPO3\Vhs\Traits\TemplateVariableViewHelperTrait;
+use Opentalent\OtCore\Domain\Repository\MemberCaRepository;
 use Opentalent\OtCore\ViewHelpers\OtAbstractViewHelper;
 use Opentalent\OtCore\ViewHelpers\OtAbstractViewHelper;
-use Opentalent\OtCore\Domain\Repository\MemberRepository;
 use Opentalent\OtCore\Exception\ApiRequestException;
 use Opentalent\OtCore\Exception\ApiRequestException;
 
 
 /**
 /**
@@ -31,10 +31,10 @@ class GetAllCaViewHelper extends OtAbstractViewHelper {
     protected $escapeOutput = false;
     protected $escapeOutput = false;
 
 
     /**
     /**
-     * @var MemberRepository
+     * @var MemberCaRepository
      *
      *
      */
      */
-    protected MemberRepository $memberRepository;
+    protected MemberCaRepository $memberCaRepository;
 
 
     /**
     /**
      * -- This method is expected by Fluid --
      * -- This method is expected by Fluid --
@@ -100,7 +100,7 @@ class GetAllCaViewHelper extends OtAbstractViewHelper {
 
 
         // Get members of the structure (only CA members)
         // Get members of the structure (only CA members)
         try {
         try {
-            $collection = $this->memberRepository->findByOrganizationId($organizationId, true);
+            $collection = $this->memberCaRepository->findByOrganizationId($organizationId);
             $members = $collection->getMembers();
             $members = $collection->getMembers();
         } catch (ApiRequestException $e) {
         } catch (ApiRequestException $e) {
             $this->logger->error(sprintf('API Error: %s', $e->getMessage()));
             $this->logger->error(sprintf('API Error: %s', $e->getMessage()));
@@ -148,10 +148,10 @@ class GetAllCaViewHelper extends OtAbstractViewHelper {
     }
     }
 
 
     /**
     /**
-     * @param MemberRepository $memberRepository
+     * @param MemberCaRepository $memberCaRepository
      */
      */
-    public function injectMemberRepository(MemberRepository $memberRepository)
+    public function injectMemberCaRepository(MemberCaRepository $memberCaRepository)
     {
     {
-        $this->memberRepository = $memberRepository;
+        $this->memberCaRepository = $memberCaRepository;
     }
     }
 }
 }

+ 7 - 6
ot_templating/Classes/ViewHelpers/Members/GetPresidentViewHelper.php

@@ -3,6 +3,7 @@
 namespace Opentalent\OtTemplating\ViewHelpers\Members;
 namespace Opentalent\OtTemplating\ViewHelpers\Members;
 
 
 use FluidTYPO3\Vhs\Traits\TemplateVariableViewHelperTrait;
 use FluidTYPO3\Vhs\Traits\TemplateVariableViewHelperTrait;
+use Opentalent\OtCore\Domain\Repository\MemberCaRepository;
 use Opentalent\OtCore\ViewHelpers\OtAbstractViewHelper;
 use Opentalent\OtCore\ViewHelpers\OtAbstractViewHelper;
 use Opentalent\OtCore\Domain\Repository\MemberRepository;
 use Opentalent\OtCore\Domain\Repository\MemberRepository;
 use Opentalent\OtCore\Exception\ApiRequestException;
 use Opentalent\OtCore\Exception\ApiRequestException;
@@ -30,10 +31,10 @@ class GetPresidentViewHelper extends OtAbstractViewHelper {
     protected $escapeOutput = false;
     protected $escapeOutput = false;
 
 
     /**
     /**
-     * @var MemberRepository
+     * @var MemberCaRepository
      *
      *
      */
      */
-    protected MemberRepository $memberRepository;
+    protected MemberCaRepository $memberCaRepository;
 
 
     /**
     /**
      * -- This method is expected by Fluid --
      * -- This method is expected by Fluid --
@@ -69,7 +70,7 @@ class GetPresidentViewHelper extends OtAbstractViewHelper {
 
 
         // Get members of the structure (only CA members)
         // Get members of the structure (only CA members)
         try {
         try {
-            $collection = $this->memberRepository->findByOrganizationId($organizationId, true);
+            $collection = $this->memberCaRepository->findByOrganizationId($organizationId);
             $members = $collection->getMembers();
             $members = $collection->getMembers();
         } catch (ApiRequestException $e) {
         } catch (ApiRequestException $e) {
             $this->logger->error(sprintf('API Error: %s', $e->getMessage()));
             $this->logger->error(sprintf('API Error: %s', $e->getMessage()));
@@ -89,10 +90,10 @@ class GetPresidentViewHelper extends OtAbstractViewHelper {
     }
     }
 
 
     /**
     /**
-     * @param MemberRepository $memberRepository
+     * @param MemberCaRepository $memberCaRepository
      */
      */
-    public function injectMemberRepository(MemberRepository $memberRepository)
+    public function injectMemberCaRepository(MemberCaRepository $memberCaRepository)
     {
     {
-        $this->memberRepository = $memberRepository;
+        $this->memberCaRepository = $memberCaRepository;
     }
     }
 }
 }