diff --git a/webapp/src/Controller/Organization/OrganizationManageController.php b/webapp/src/Controller/Organization/OrganizationManageController.php new file mode 100644 index 0000000000000000000000000000000000000000..54cbac8f9469003bae9d2884ece6e876fe2fce68 --- /dev/null +++ b/webapp/src/Controller/Organization/OrganizationManageController.php @@ -0,0 +1,216 @@ +<?php + +/* + * This file is part of the Comptoir-du-Libre software. + * <https://gitlab.adullact.net/Comptoir/comptoir-du-libre> + * + * Copyright (c) ADULLACT <https://adullact.org> + * Association des Développeurs et Utilisateurs de Logiciels Libres + * pour les Administrations et les Collectivités Territoriales + * + * Comptoir-du-Libre is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * You should have received a copy of the GNU Affero General Public License + * along with this software. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>. + */ + +declare(strict_types=1); + +namespace App\Controller\Organization; + +use App\Entity\Organization; +use App\Entity\OrganizationI18n; +use App\Entity\OrganizationType; +use App\Form\Organization\OrganizationFormType; +use App\Repository\OrganizationI18nRepository; +use App\Repository\OrganizationRepository; +use App\Repository\OrganizationTypeRepository; +use Symfony\Bridge\Twig\Attribute\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\String\Slugger\AsciiSlugger; +use Symfony\Contracts\Translation\TranslatorInterface; + +use function Symfony\Component\String\u; + +#[Route( + requirements: [ + '_locale' => 'en|fr', + ], +)] +class OrganizationManageController extends AbstractController +{ + #[Route( + path: [ + 'en' => '/{_locale}/organizations/new', + 'fr' => '/{_locale}/organisations/new', + ], + name: 'app_connected_user_add_organization', + methods: ['GET', 'POST'] + )] + public function createNewOrganization( + string $_locale, + Request $request, + FormFactoryInterface $formFactory, + TranslatorInterface $translator, + OrganizationRepository $organizationRepository, + OrganizationI18nRepository $organizationI18nRepository, + ): Response { + $sessionFormToken = '11111'; + $form = $formFactory->createNamed( + name: "new_organization_$sessionFormToken", + type: OrganizationFormType::class, + data: new Organization(), + ); + $form->handleRequest($request); + + + if ($form->isSubmitted() && $form->isValid()) { + $orgType = $form->get('type')->getData(); + $orgName = $form->get('name')->getData(); + $orgWebsite = $form->get('website')->getData(); + + $slugger = new AsciiSlugger(); + $slug = u($slugger->slug("$orgName")->toString())->lower()->toString(); + + // Add new user + $now = new \DateTimeImmutable(); + $organization = new Organization(); + $organization->setUpdatedAt($now); + $organization->setCreatedAt($now); + $organization->setType($orgType); + $organization->setName($orgName); + $organization->setWebsite($orgWebsite); + $organization->setSlug($slug); + $organizationRepository->save($organization, true); + + if (!empty($form->get('description')->getData())) { + $organizationI18n = new OrganizationI18n(); + $organizationI18n->setOrganization($organization); + $organizationI18n->setLocale($_locale); + $organizationI18n->setType(1); + $organizationI18n->setText($form->get('description')->getData()); + $organizationI18nRepository->save($organizationI18n, true); + } + + $this->addFlash( + 'success', + $translator->trans('organization.update_form.success', ['organization_name' => $orgName]), + ); + return $this->redirectToRoute('app_anonymous_organization_display_one_organization', [ + '_locale' => $_locale, + 'id' => $organization->getId(), + 'slug' => $organization->getSlug(), + ]); + + // https://symfony.com/doc/current/reference/forms/types/file.html#mapped + // https://symfony.com/doc/6.4/controller/upload_file.html + } + + // Display form (first display or user errors in form) + return $this->render( + view: 'webapp/organization/form_create_organization.html.twig', + parameters: [ + 'organizationForm' => $form->createView(), + ] + ); + } + + + #[Route( + path: [ + 'en' => '/{_locale}/organization/{id}/{slug}/edit', + 'fr' => '/{_locale}/organisation/{id}/{slug}/edit', + ], + name: 'app_connected_user_update_organization', + methods: ['GET', 'POST'] + )] + public function updateOrganization( + string $_locale, + Organization $organization, + OrganizationRepository $organizationRepository, + OrganizationI18nRepository $organizationI18nRepository, + Request $request, + TranslatorInterface $translator, + ): Response|RedirectResponse { + + $organizationI18n = $organizationI18nRepository->findOneBy([ + 'organization' => $organization->getId(), + 'locale' => $_locale, + 'type' => 1 + ]); // @@@TODO improve it: if empty --> get other local + // $organization->getOrganizationI18ns(); + + $organizationDescription = ''; + if (is_object($organizationI18n)) { + $organizationDescription = $organizationI18n->getText(); + } else { + $organizationI18n = new OrganizationI18n(); + $organizationI18n->setOrganization($organization); + $organizationI18n->setLocale($_locale); + $organizationI18n->setType(1); + } + + $form = $this->createForm( + type: OrganizationFormType::class, + data: $organization, + options: ['organization_description' => $organizationDescription] + ); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $orgType = $form->get('type')->getData(); + $orgName = $form->get('name')->getData(); + $orgWebsite = $form->get('website')->getData(); + + $slugger = new AsciiSlugger(); + $slug = u($slugger->slug("$orgName")->toString())->lower()->toString(); + + if (!empty($form->get('description')->getData())) { + $organizationI18n->setText($form->get('description')->getData()); + $organizationI18nRepository->save($organizationI18n, true); + } + $now = new \DateTimeImmutable(); + $organization->setUpdatedAt($now); + $organization->setType($orgType); + // $organization->setName($orgName); + // $organization->setWebsite($orgWebsite); + $organization->setSlug($slug); + // $organization->addOrganizationI18n($organizationDescription); + $organizationRepository->save($organization, true); + + // Redirect user and display success message + $this->addFlash( + 'success', + $translator->trans('organization.update_form.success', ['organization_name' => $orgName]), + ); + return $this->redirectToRoute('app_anonymous_organization_display_one_organization', [ + '_locale' => $_locale, + 'id' => $organization->getId(), + 'slug' => $organization->getSlug(), + ]); + + // https://symfony.com/doc/current/reference/forms/types/file.html#mapped + // https://symfony.com/doc/6.4/controller/upload_file.html + } + + + // Display form (first display or user errors in form) + return $this->render( + view: 'webapp/organization/form_update_organization.html.twig', + parameters: [ + 'organizationForm' => $form->createView(), + '_locale' => $_locale, + 'organization' => $organization, + 'organization_type' => $organization->getType()->getName(), + ], + ); + } +} diff --git a/webapp/src/Form/Organization/OrganizationFormType.php b/webapp/src/Form/Organization/OrganizationFormType.php new file mode 100644 index 0000000000000000000000000000000000000000..3375e8ff2992847c91cc0417c68a340ddaaace21 --- /dev/null +++ b/webapp/src/Form/Organization/OrganizationFormType.php @@ -0,0 +1,186 @@ +<?php + +/* + * This file is part of the Comptoir-du-Libre software. + * <https://gitlab.adullact.net/Comptoir/comptoir-du-libre> + * + * Copyright (c) ADULLACT <https://adullact.org> + * Association des Développeurs et Utilisateurs de Logiciels Libres + * pour les Administrations et les Collectivités Territoriales + * + * Comptoir-du-Libre is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * You should have received a copy of the GNU Affero General Public License + * along with this software. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>. + */ + +declare(strict_types=1); + +namespace App\Form\Organization; + +use App\Entity\OrganizationType; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ResetType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\CountryType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\UrlType; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Translation\TranslatableMessage; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\File; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\Url; +use Symfony\Component\Validator\Constraints\Hostname; + +class OrganizationFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add( + child: 'type', + type: EntityType::class, + options: array( + 'label' => 'organization.form.label.org_type', + 'help' => 'organization.form.label.org_type.help', + 'class' => OrganizationType::class, + 'choice_translation_domain' => true, + 'choice_label' => function (?OrganizationType $organizationType): string { + $labelPrefix = 'organization.form.org_type.value.'; + return $organizationType ? $labelPrefix . $organizationType->getName() : ''; + }, + 'expanded' => true, + ) + ) + ->add( + child: 'countryName', + type: CountryType::class, + options: [ + // 'required' => false, // TODO FIXME + 'mapped' => false, // TODO FIXME + 'attr' => [ // HTML attribut + 'disabled' => true, + ], + 'label' => 'organization.form.label.country', + 'help' => 'organization.form.label.country.help', + 'preferred_choices' => ['France', 'FR' ], + // 'constraints' => [new NotBlank(['message' => 'common.validator.help.field.error.required'])], + ] + ) + ->add( + child: 'name', + type: TextType::class, + options: [ + 'label' => 'organization.form.label.name', + 'help' => 'organization.form.label.name.help', + // 'sanitize_html' => true, + // 'sanitizer' => 'user-data_remove_html', + 'constraints' => [new NotBlank(['message' => 'common.validator.help.field.error.required'])], + ] + ) + ->add( + child: 'siret', + type: TextType::class, + options: [ + 'required' => false, + 'mapped' => false, // TODO FIXME + 'attr' => [ // HTML attribut + 'disabled' => true, + ], + 'label' => 'organization.form.label.siret', + 'help' => 'organization.form.label.siret.help', + ] + ) + ->add( + child: 'website', + type: UrlType::class, + options: [ + 'required' => false, + 'label' => 'organization.form.label.website', + 'help' => 'organization.form.label.website.help', + 'attr' => [ // HTML attribut + 'placeholder' => 'https://example.org', + ], + // 'sanitize_html' => true, + // 'sanitizer' => 'user-data_remove_html', + 'constraints' => [ + // new Url(protocols: ['http', 'https'], relativeProtocol: false, normalizer: 'strip_tags'), + new Url(protocols: ['http', 'https'], relativeProtocol: false), + ], + ] + ) + ->add( + child: 'logo', + type: FileType::class, + options: [ + 'mapped' => false, // TODO FIXME + 'required' => false, + 'label' => 'organization.form.label.logo', + 'help' => 'organization.form.label.logo.help', + 'multiple' => false, + 'attr' => [ // HTML attribut + 'accept' => "image/png, image/jpeg", + 'disabled' => true, + ], + 'constraints' => [ + new File([ + // 'maxSize' => '1024k', + 'maxSize' => '1m', + 'mimeTypes' => [ + 'image/jpeg', + 'image/png', + // 'image/gif', + ], + 'mimeTypesMessage' => 'organization.form.label.logo.help.error.not_valid_file', + ]) + ], + ] + ) + ->add( + child: 'description', + type: TextareaType::class, + options:[ + 'required' => false, + 'mapped' => false, + // 'sanitize_html' => true, + // 'sanitizer' => 'user-data_remove_html', + 'attr' => [ // HTML attribut + 'rows' => 6, + ], + 'data' => $options['organization_description'], + 'label' => 'organization.form.label.description', + 'help' => 'organization.form.label.description.help', + ] + ) + ->add('reset', ResetType::class, [ + 'attr' => ['class' => 'save'], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'organization_description' => '', + + // enable/disable CSRF protection for this form + 'csrf_protection' => true, + // the name of the hidden HTML field that stores the token + 'csrf_field_name' => 'csrf_token_ChangePassword', + // an arbitrary string used to generate the value of the token + // using a different string for each form improves its security + 'csrf_token_id' => 'task_ChangePasswordFormType', + ]); + + // you can also define the allowed types, allowed values and + // any other feature supported by the OptionsResolver component + $resolver->setAllowedTypes('organization_description', [ 'null', 'string']); + } +} diff --git a/webapp/templates/webapp/organization/form_create_organization.html.twig b/webapp/templates/webapp/organization/form_create_organization.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..5e7c0179b5f0ccc9eaf678271ff3f7813b7c7495 --- /dev/null +++ b/webapp/templates/webapp/organization/form_create_organization.html.twig @@ -0,0 +1,51 @@ +{% extends 'app_base.html.twig' %} +{% form_theme organizationForm 'bootstrap_5_layout.html.twig' %} + +{% block title %} {{ 'organization.add_form.head.title'|trans }} - {{ app_name }}{% endblock %} +{% block body %} + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"> + <a href="{{ path('app_home_i18n') }}">{{ 'breadcrumb.homepage'|trans }}</a> + </li> + <li class="breadcrumb-item active"> + <a href="{{ path('app_connected_user_add_organization') }}" + aria-current="page">{{ 'breadcrumb.organization.form.create'|trans }}</a> + </li> + </ol> + </nav> + + <h1> + {{ 'organization.add_form.page.title'|trans }} + </h1> + + <div class="col-md-8"> +{# {{ form_start(organizationForm) }} #} +{# {{ form_start(organizationForm, {'attr': {'novalidate': 'novalidate'}}) }} #} + {{ form_start(organizationForm) }} + + {{ form_row(organizationForm.countryName) }} + {{ form_row(organizationForm.type) }} + {{ form_row(organizationForm.name) }} + {{ form_row(organizationForm.siret) }} + + {{ form_row(organizationForm.website) }} + {{ form_row(organizationForm.logo) }} + {{ form_row(organizationForm.description) }} + + + {# {{ form_row(organizationForm.org_siret) }}#} + {# <fieldset class="mt-4 mb-4 ">#} + {# <legend class="text-xl text-bg-dark px-2">#} + {# {{ 'signup.form.fieldset.user.title'|trans }}#} + {# </legend>#} + {# {{ form_row(organizationForm.login) }}#} + {# </fieldset>#} + + <button id="public_organizationForm_submit" class="btn btn-primary"> + {{ 'organization.add_form.submit'|trans|raw }} + </button> + {{ form_end(organizationForm) }} + </div> + +{% endblock %} diff --git a/webapp/templates/webapp/organization/form_update_organization.html.twig b/webapp/templates/webapp/organization/form_update_organization.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..37d68e31018e40edb4dd848ff89ec65b04693c4c --- /dev/null +++ b/webapp/templates/webapp/organization/form_update_organization.html.twig @@ -0,0 +1,71 @@ +{% extends 'app_base.html.twig' %} +{% form_theme organizationForm 'bootstrap_5_layout.html.twig' %} + +{% block title %} {{ 'organization.update_form.head.title'|trans }} - {{ app_name }}{% endblock %} +{% block body %} + <nav aria-label="breadcrumb"> + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"> + <a href="{{ path('app_home_i18n') }}"> + {{ 'breadcrumb.homepage'|trans }}</a> + </li> +{# <li class="breadcrumb-item">#} +{# <a href="{{ path('app_anonymous_organization_display_all_organization') }}">#} +{# {{ 'breadcrumb.organization.list'|trans }}</a>#} +{# </li>#} + <li class="breadcrumb-item"> + <a href="{{ path("app_anonymous_organization_display_all_org_#{organization_type}") }}"> + {{ "breadcrumb.org.#{organization_type}.list"|trans }}</a> + </li> + <li class="breadcrumb-item"> + <a href="{{ path( + 'app_anonymous_organization_display_one_organization', + {'id': organization.id, 'slug': organization.slug } + ) }}">{{ organization.name }}</a> + </li> + <li class="breadcrumb-item active" aria-current="page"> + <a href="{{ path( + 'app_connected_user_update_organization', + {'id': organization.id, 'slug': organization.slug } + ) }}">{{ 'breadcrumb.organization.form.edit'|trans }}</a> + </li> + </ol> + </nav> + </nav> + + <h1> + {{ 'organization.update_form.page.title'|trans({'organization_name': organization.name}) }} + </h1> + <div class="col-md-8"> +{# {{ form_start(organizationForm) }} #} +{# {{ form_start(organizationForm, {'attr': {'novalidate': 'novalidate'}}) }} #} + {{ form_start(organizationForm) }} + + {{ form_row(organizationForm.countryName) }} + {{ form_row(organizationForm.type) }} + {{ form_row(organizationForm.name) }} + {{ form_row(organizationForm.siret) }} + + {{ form_row(organizationForm.website) }} + {{ form_row(organizationForm.logo) }} + {{ form_row(organizationForm.description) }} + + +{# {{ form_row(organizationForm.org_siret) }}#} +{# <fieldset class="mt-4 mb-4 ">#} +{# <legend class="text-xl text-bg-dark px-2">#} +{# {{ 'signup.form.fieldset.user.title'|trans }}#} +{# </legend>#} +{# {{ form_row(organizationForm.login) }}#} +{# </fieldset>#} + + + + <button id="public_organizationForm_submit" class="btn btn-primary"> + {{ 'organization.update_form.submit'|trans|raw }} + </button> + {{ form_end(organizationForm) }} + </div> + +{% endblock %} diff --git a/webapp/tests/Functional/Organization/FunctionalTestOrganizationPagesTest.php b/webapp/tests/Functional/Organization/FunctionalTestOrganizationPagesTest.php index c10d8f97c641ff75c08e72cb694c4bfefd6347ab..c09cc4646819892a25542bf5ee67e84171d76a75 100644 --- a/webapp/tests/Functional/Organization/FunctionalTestOrganizationPagesTest.php +++ b/webapp/tests/Functional/Organization/FunctionalTestOrganizationPagesTest.php @@ -24,6 +24,7 @@ use App\DataFixtures\AppOrganizationFixtures; use App\Tests\Functional\TestHelperBreadcrumbTrait; use App\Tests\Functional\TestHelperTrait; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Response; @@ -36,6 +37,7 @@ class FunctionalTestOrganizationPagesTest extends WebTestCase use TestHelperTrait; use TestHelperBreadcrumbTrait; + /////////////////////////////////////////////////////////////////////////////////// public function testAllOrganizationsPageAllowToChangeLocaleInUrl(): void { @@ -56,6 +58,70 @@ public function testAllOrganizationsPageAllowToChangeLocaleInUrl(): void ); } + /////////////////////////////////////////////////////////////////////////////////// + + public function testAllPublicSectorOrganizationsPageAllowToChangeLocaleInUrl(): void + { + $client = static::createClient(); + $type = "public_sector"; + $this->checkRedirectionToNewUrl( + client: $client, + currentUrl: "/en/organisations/administration/", + expectedNewUrl: "/en/organizations/public-sector/", + expectedCurrentRoute: "app_anonymous_organization_display_all_${type}_allow_to_change_locale_in_url", + expectedNewRoute: "app_anonymous_organization_display_all_org_$type", + ); + $this->checkRedirectionToNewUrl( + client: $client, + currentUrl: "/fr/organizations/public-sector/", + expectedNewUrl: "/fr/organisations/administration/", + expectedCurrentRoute: "app_anonymous_organization_display_all_${type}_allow_to_change_locale_in_url", + expectedNewRoute: "app_anonymous_organization_display_all_org_$type", + ); + } + + public function testAllNoProfitOrganizationsPageAllowToChangeLocaleInUrl(): void + { + $client = static::createClient(); + $type = "no_profit"; + $this->checkRedirectionToNewUrl( + client: $client, + currentUrl: "/en/organisations/association/", + expectedNewUrl: "/en/organizations/non-profit/", + expectedCurrentRoute: "app_anonymous_organization_display_all_${type}_allow_to_change_locale_in_url", + expectedNewRoute: "app_anonymous_organization_display_all_org_$type", + ); + $this->checkRedirectionToNewUrl( + client: $client, + currentUrl: "/fr/organizations/non-profit/", + expectedNewUrl: "/fr/organisations/association/", + expectedCurrentRoute: "app_anonymous_organization_display_all_${type}_allow_to_change_locale_in_url", + expectedNewRoute: "app_anonymous_organization_display_all_org_$type", + ); + } + + public function testAllCompaniesPageAllowToChangeLocaleInUrl(): void + { + $client = static::createClient(); + $type = "company"; + $this->checkRedirectionToNewUrl( + client: $client, + currentUrl: "/en/organisations/entreprises/", + expectedNewUrl: "/en/organizations/companies/", + expectedCurrentRoute: "app_anonymous_organization_display_all_${type}_allow_to_change_locale_in_url", + expectedNewRoute: "app_anonymous_organization_display_all_org_$type", + ); + $this->checkRedirectionToNewUrl( + client: $client, + currentUrl: "/fr/organizations/companies/", + expectedNewUrl: "/fr/organisations/entreprises/", + expectedCurrentRoute: "app_anonymous_organization_display_all_${type}_allow_to_change_locale_in_url", + expectedNewRoute: "app_anonymous_organization_display_all_org_$type", + ); + } + + /////////////////////////////////////////////////////////////////////////////////// + public function testOneOrganizationPageAllowToTruncateUrlToRedirectToAllOrganizationsPage(): void { $client = static::createClient(); @@ -75,7 +141,6 @@ public function testOneOrganizationPageAllowToTruncateUrlToRedirectToAllOrganiza ); } - public function testOneOrganizationPageAllowToChangeLocaleInUrl(): void { $organizationId = AppOrganizationFixtures::ORGANIZATIONS[3]['id']; @@ -97,7 +162,6 @@ public function testOneOrganizationPageAllowToChangeLocaleInUrl(): void ); } - public function testOneOrganizationPageWithoutSlugRedirectToValidUrl(): void { $organizationId = AppOrganizationFixtures::ORGANIZATIONS[3]['id']; @@ -140,33 +204,388 @@ public function testOneOrganizationPageWithBadSlugRedirectToValidUrl(): void ); } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + public function testAllOrganizationsPageDisplay(): void + { + $this->commonCheckerAllOrganizationsPageDisplay( + locale: 'en', + expectedTitle: 'Organizations: public sector, associations and companies - Comptoir du Libre TEST', + expectedH1: 'Organizations', + ); + } + + public function testAllOrganizationsPageDisplayForFrenchLocale(): void + { + $this->commonCheckerAllOrganizationsPageDisplay( + locale: 'fr', + expectedTitle: 'Organisations - Comptoir du Libre TEST', + expectedH1: 'Organisations', + ); + } + + private function commonCheckerAllOrganizationsPageDisplay( + string $locale, + string $expectedTitle, + string $expectedH1, + ): void { + $i18nUrlOneOrganization = 'organization'; + $i18nUrlAllOrganizations = 'organizations'; + $i18nTextNumberOfOrganizations = 'organizations'; + if ($locale === 'fr') { + $i18nUrlOneOrganization = 'organisation'; + $i18nUrlAllOrganizations = 'organisations'; + $i18nTextNumberOfOrganizations = 'organisations'; + } + + $i18nUrlAllOrganizations = "/$locale/$i18nUrlAllOrganizations/"; + $organizations = AppOrganizationFixtures::ORGANIZATIONS; + + // Basic checks + $client = static::createClient(); + $crawler = $client->request('GET', "$i18nUrlAllOrganizations"); + $this->assertRouteSame('app_anonymous_organization_display_all_organization'); + $this->assertResponseStatusCodeSame(Response::HTTP_OK); // HTTP status code = 200 + $this->assertSelectorTextSame('h1', "$expectedH1"); + $this->assertPageTitleSame($expectedTitle); + + // HTML Breadcrumb + $breadcrumbLinks = [ + "$i18nUrlAllOrganizations" => "$expectedH1", + ]; + $this->checkHasValidBreadcrumb($crawler, $breadcrumbLinks, "$locale"); + + // HTML - Organization list + $this->assertSelectorCount(1, "ul.organization_collection"); + $numberOfOrganizations = \count($crawler->filter("ul.organization_collection li")); + $this->assertGreaterThanOrEqual(count(AppOrganizationFixtures::ORGANIZATIONS), $numberOfOrganizations); + $this->assertSelectorTextContains( + '#organization_count', + "$numberOfOrganizations $i18nTextNumberOfOrganizations", // example: "6 organizations" + ); + foreach ($organizations as $key => $organization) { + $organizationId = AppOrganizationFixtures::ORGANIZATIONS[$key]['id']; + $organizationSlug = AppOrganizationFixtures::ORGANIZATIONS[$key]['slug']; + + // TODO Fixme + $this->checkAttribute( + crawler: $crawler, + cssFilter: "ul.organization_collection li.organization_$organizationId a", + attributesExpected: [ + '_name' => 'a', + '_text' => AppOrganizationFixtures::ORGANIZATIONS[$key]['name'], + 'href' => "/$locale/$i18nUrlOneOrganization/$organizationId/$organizationSlug/" + ] + ); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + + + public function testAllPublicSectorOrganizationsPageDisplay(): void + { + $this->commonCheckerAllOrganizationsByTypePageDisplay( + locale: 'en', + type: 'public_sector', + ); + } + public function testAllPublicSectorOrganizationsPageDisplayForFrenchLocale(): void + { + $this->commonCheckerAllOrganizationsByTypePageDisplay( + locale: 'fr', + type: 'public_sector', + ); + } + + public function testAllNoProfitOrganizationsPageDisplay(): void + { + $this->commonCheckerAllOrganizationsByTypePageDisplay( + locale: 'en', + type: 'no_profit', + ); + } + public function testAllNoProfitOrganizationsPageDisplayForFrenchLocale(): void + { + $this->commonCheckerAllOrganizationsByTypePageDisplay( + locale: 'fr', + type: 'no_profit', + ); + } + + public function testAllCompaniesOrganizationsPageDisplay(): void + { + $this->commonCheckerAllOrganizationsByTypePageDisplay( + locale: 'en', + type: 'company', + ); + } + public function testAllCompaniesOrganizationsPageDisplayForFrenchLocale(): void + { + $this->commonCheckerAllOrganizationsByTypePageDisplay( + locale: 'fr', + type: 'company', + ); + } + + private function commonCheckerAllOrganizationsByTypePageDisplay( + string $locale, + string $type, + ): void { + $appName = 'Comptoir du Libre TEST'; + $i18nUrlOneOrganization = 'organization'; + $i18nUrlAllOrganizations = 'organizations'; + $i18nOrganisationTypes = [ + 'public_sector' => [ + 'inUrl' => 'public-sector', + 'title' => "Public sector: local authorities, administrations… - $appName", + 'h1' => 'Public sector', + 'countText' => 'local authorities, administrations and other public services', + 'breadcrumbAllOrganizations' => 'Organizations', + 'breadcrumbAllOrganizationsByType' => 'Public sector', + ], + 'no_profit' => [ + 'inUrl' => 'non-profit', + 'title' => "Associations - $appName", // TODO i18n + 'h1' => 'Associations', // TODO i18n + 'countText' => 'associations', // TODO i18n + 'breadcrumbAllOrganizations' => 'Organizations', + 'breadcrumbAllOrganizationsByType' => 'Associations', + ], + 'company' => [ + 'inUrl' => 'companies', + 'title' => "Companies - $appName", + 'h1' => 'Companies', + 'countText' => 'companies', + 'breadcrumbAllOrganizations' => 'Organizations', + 'breadcrumbAllOrganizationsByType' => 'Companies', + ], + ]; + if ($locale === 'fr') { + $i18nUrlOneOrganization = 'organisation'; + $i18nUrlAllOrganizations = 'organisations'; + $i18nOrganisationTypes = [ + 'public_sector' => [ + 'inUrl' => 'administration', + 'title' => "Services Publics : collectivités territoriales, administrations… - $appName", + 'h1' => 'Services Publics', + 'countText' => 'collectivités territoriales, administrations et autres services publics', + 'breadcrumbAllOrganizations' => 'Organisations', + 'breadcrumbAllOrganizationsByType' => 'Services Publics', + ], + 'no_profit' => [ + 'inUrl' => 'association', + 'title' => "Associations - $appName", // TODO i18n + 'h1' => 'Associations', // TODO i18n + 'countText' => 'associations', // TODO i18n + 'breadcrumbAllOrganizations' => 'Organisations', + 'breadcrumbAllOrganizationsByType' => 'Associations', + ], + 'company' => [ + 'inUrl' => 'entreprises', + 'title' => "Entreprises - $appName", + 'h1' => 'Entreprises', + 'countText' => 'entreprises', + 'breadcrumbAllOrganizations' => 'Organisations', + 'breadcrumbAllOrganizationsByType' => 'Entreprises', + ], + ]; + } + + + $expectedTitle = $i18nOrganisationTypes[$type]['title']; + $expectedH1 = $i18nOrganisationTypes[$type]['h1']; + $i18nTextNumberOfOrganizations = $i18nOrganisationTypes[$type]['countText']; + $i18nUrlAllOrganizations = "/$locale/$i18nUrlAllOrganizations/"; + $i18nUrlAllOrganizationsByType = $i18nUrlAllOrganizations . $i18nOrganisationTypes[$type]['inUrl'] . '/'; + $organizations = AppOrganizationFixtures::ORGANIZATIONS; + + // Basic checks + $client = static::createClient(); + $crawler = $client->request('GET', "$i18nUrlAllOrganizationsByType"); + $this->assertRouteSame("app_anonymous_organization_display_all_org_$type",); + $this->assertResponseStatusCodeSame(Response::HTTP_OK); // HTTP status code = 200 + $this->assertSelectorTextSame('h1', "$expectedH1"); + $this->assertPageTitleSame($expectedTitle); + + // HTML Breadcrumb + $breadcrumbLinks = [ + "$i18nUrlAllOrganizations" => $i18nOrganisationTypes[$type]['breadcrumbAllOrganizations'], + "$i18nUrlAllOrganizationsByType" => $i18nOrganisationTypes[$type]['breadcrumbAllOrganizationsByType'], + ]; + $this->checkHasValidBreadcrumb($crawler, $breadcrumbLinks, "$locale"); + + // HTML - Organization list + $this->assertSelectorCount(1, "ul.organization_collection"); + $numberOfOrganizations = \count($crawler->filter("ul.organization_collection li")); + $this->assertSelectorTextContains( + '#organization_count', + "$numberOfOrganizations $i18nTextNumberOfOrganizations", // example: "6 organizations" + ); + $expectedNumberOfOrganizations = 0; + foreach ($organizations as $key => $organization) { + $organizationId = AppOrganizationFixtures::ORGANIZATIONS[$key]['id']; + $organizationType = AppOrganizationFixtures::ORGANIZATIONS[$key]['type']; + $organizationSlug = AppOrganizationFixtures::ORGANIZATIONS[$key]['slug']; + if ($organizationType === $type) { + $expectedNumberOfOrganizations++; + + // TODO fixme + $this->checkAttribute( + crawler: $crawler, + cssFilter: "ul.organization_collection li.organization_$organizationId a", + attributesExpected: [ + '_name' => 'a', + '_text' => AppOrganizationFixtures::ORGANIZATIONS[$key]['name'], + 'href' => "/$locale/$i18nUrlOneOrganization/$organizationId/$organizationSlug/" + ] + ); + } else { + $this->assertSelectorCount( + expectedCount: 0, + selector: "ul.organization_collection li.organization_$organizationId" + ); + } + } + $this->assertGreaterThanOrEqual($expectedNumberOfOrganizations, $numberOfOrganizations); + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////: + + /** + * @group gogogo3 + */ public function testOneOrganizationPageDisplay(): void { - $locale = 'en'; - $urlOrganizationPrefixSingle = 'organization'; - $urlOrganizationPrefixPlural = 'organizations'; - $organizationId = AppOrganizationFixtures::ORGANIZATIONS[3]['id']; + $kernelBrowser = static::createClient(); + $this->commonCheckerOneOrganizationPageDisplay($kernelBrowser, 'en', 1); // no profit + $this->commonCheckerOneOrganizationPageDisplay($kernelBrowser, 'en', 6); // public sector + $this->commonCheckerOneOrganizationPageDisplay($kernelBrowser, 'en', 10); // private sector + + $this->markTestIncomplete(); + // HTML - Publishing informations + // HTML - Organization WEBSITE + // HTML - Organization DESCRIPTION + } + + /** + * @group gogogo3 + */ + public function testOneOrganizationPageDisplayForFrenchLocale(): void + { + $kernelBrowser = static::createClient(); + $this->commonCheckerOneOrganizationPageDisplay($kernelBrowser, 'fr', 1); // no profit + $this->commonCheckerOneOrganizationPageDisplay($kernelBrowser, 'fr', 6); // public sector + $this->commonCheckerOneOrganizationPageDisplay($kernelBrowser, 'fr', 10); // private sector + + $this->markTestIncomplete(); + // HTML - Publishing informations + // HTML - Organization WEBSITE + // HTML - Organization DESCRIPTION + } + + private function commonCheckerOneOrganizationPageDisplay( + KernelBrowser $kernelBrowser, + string $locale, + int $organizationKey, + ): void { + $i18nConfig = [ + 'i18nUrlPrefix_organizationSingle' => 'organization', + 'i18nUrlPrefix_organizationPlural' => 'organizations', + 'public_sector' => [ + 'i18nUrl_organizationPrefix' => 'public-sector', + 'i18nUrl_organizationType' => 'public-sector', + 'i18nBreadcrumb_organizationType' => 'Public sector', + ], + 'no_profit' => [ + 'i18nUrl_organizationType' => 'non-profit', // TODO i18n fixme + 'i18nBreadcrumb_organizationType' => 'Associations', // TODO i18n fixme + ], + 'company' => [ + 'i18nUrl_organizationType' => 'companies', + 'i18nBreadcrumb_organizationType' => 'Companies', + ], + ]; + if ($locale === 'fr') { + $i18nConfig = [ + 'i18nUrlPrefix_organizationSingle' => 'organisation', + 'i18nUrlPrefix_organizationPlural' => 'organisations', + 'public_sector' => [ + 'i18nUrl_organizationType' => 'administration', // TODO i18n fixme + 'i18nBreadcrumb_organizationType' => 'Services Publics', + ], + 'no_profit' => [ + 'i18nUrl_organizationType' => 'association', // TODO i18n fixme + 'i18nBreadcrumb_organizationType' => 'Associations', // TODO i18n fixme + ], + 'company' => [ + 'i18nUrl_organizationType' => 'entreprises', + 'i18nBreadcrumb_organizationType' => 'Entreprises', + ], + ]; + } + + + $organizationId = AppOrganizationFixtures::ORGANIZATIONS[$organizationKey]['id']; $organizationSlug = AppOrganizationFixtures::ORGANIZATIONS[$organizationId]['slug']; $organizationName = AppOrganizationFixtures::ORGANIZATIONS[$organizationId]['name']; - $organizationWebsite = AppOrganizationFixtures::ORGANIZATIONS[$organizationId]['website']; $organizationType = AppOrganizationFixtures::ORGANIZATIONS[$organizationId]['type']; + $organizationLogo = ""; + if (isset(AppOrganizationFixtures::ORGANIZATIONS[$organizationId]['logo'])) { + $organizationLogo = AppOrganizationFixtures::ORGANIZATIONS[$organizationId]['logo']; + } + $organizationWebsite = ""; + if (isset(AppOrganizationFixtures::ORGANIZATIONS[$organizationId]['website'])) { + $organizationWebsite = AppOrganizationFixtures::ORGANIZATIONS[$organizationId]['website']; + } + $urlOrganizationPrefixSingle = $i18nConfig['i18nUrlPrefix_organizationSingle']; + $urlOrganizationPrefixPlural = $i18nConfig['i18nUrlPrefix_organizationPlural']; $organizationUrl = "/$locale/$urlOrganizationPrefixSingle/$organizationId/$organizationSlug/"; - $client = static::createClient(); - $crawler = $client->request('GET', "$organizationUrl"); + // Basic cheks + $crawler = $kernelBrowser->request('GET', "$organizationUrl"); $this->assertRouteSame('app_anonymous_organization_display_one_organization'); $this->assertResponseStatusCodeSame(Response::HTTP_OK); // HTTP status code = 200 + $this->assertSelectorTextSame('h1', "$organizationName"); + $this->assertPageTitleSame("$organizationName - Comptoir du Libre TEST"); - // HTML content checks breadcrumb + // HTML - Breadcrumb + $urlOrganizationType = $i18nConfig["$organizationType"]['i18nUrl_organizationType']; + $breadcrumbOrganizationType = $i18nConfig["$organizationType"]['i18nBreadcrumb_organizationType']; $breadcrumbLinks = [ - "/$locale/$urlOrganizationPrefixPlural/public-sector/" => "Public sector", + "/$locale/$urlOrganizationPrefixPlural/$urlOrganizationType/" => $breadcrumbOrganizationType, "$organizationUrl" => "$organizationName", ]; $this->checkHasValidBreadcrumb($crawler, $breadcrumbLinks, "$locale"); - $this->markTestIncomplete(); + // HTML - Organization LOGO + $this->assertSelectorCount(1, "img.qa_organization_logo"); + if ($organizationLogo === '') { + $this->checkAttribute( + crawler: $crawler, + cssFilter: "img.qa_organization_logo_placeholder", + attributesExpected: [ + 'class' => "qa_organization_logo qa_organization_logo_placeholder", + 'src' => "/images/placeholder_organization.png", + 'loading' => "lazy", + 'alt' => "", + ] + ); + } else { + $this->assertSelectorCount(expectedCount: 0, selector: ".qa_organization_logo_placeholder"); + $this->checkAttribute( + crawler: $crawler, + cssFilter: "img.qa_organization_logo", + attributesExpected: [ + 'src' => "/images/content/organization/$organizationId/logo/$organizationLogo", + 'loading' => "lazy", + 'alt' => "$organizationName", + ] + ); + } + // HTML - Publishing informations + // HTML - Organization WEBSITE + // HTML - Organization DESCRIPTION } - - } diff --git a/webapp/translations/messages+intl-icu.fr.yaml b/webapp/translations/messages+intl-icu.fr.yaml index bb36f9662da97307990d65d7b9009756411b8013..a54c74018473bd708e9b202a0412308469aa3bda 100644 --- a/webapp/translations/messages+intl-icu.fr.yaml +++ b/webapp/translations/messages+intl-icu.fr.yaml @@ -134,6 +134,43 @@ user.account.data-title.created_at: 'Création du compte' user.account.data-title.updated_at: 'Mise à jour du compte' user.account.data-title.last_update_password: 'Mise à jour du mot de passe' ################################################################################################################## +user.account_not_activated.msg: "Votre compte utilisateur n'est pas encore activé." +################################################################################################################## +user.account.organizations.title: 'Organisations' +user.organizations.create_organization: 'Créer une nouvelle organisation' +user.organizations.update_organization: 'Modifier' +################################################################################################################## +breadcrumb.organization.form.create: 'Nouvelle organisation' +breadcrumb.organization.form.edit: 'Modifier' +################################################################################################################## +organization.update_form.head.title: 'Modifier "{ organization_name }"' +organization.update_form.page.title: 'Modifier [ { organization_name } ]' +organization.update_form.submit: "<strong>Sauvegarder</strong> les modifications" +organization.update_form.success: 'Données sauvegardées pour [ { organization_name } ]' +organization.add_form.head.title: 'Ajouter une organisation' +organization.add_form.page.title: 'Ajouter une organisation' +organization.add_form.submit: "<strong>Ajouter</strong> l'organisation" +organization.form.org_type.value.public_sector: "Secteur public : collectivité locale, administration centrale, .." +organization.form.org_type.value.no_profit: "Secteur à but non lucratif : association, ONG" +organization.form.org_type.value.company: "Secteur privé : entreprise, freelance" +organization.form.label.org_type: "Type d'organisation" +organization.form.label.org_type.help: "Type d'organisation" +organization.form.label.country: 'Pays' +organization.form.label.country.help: "Pays associé à l'organisation." +organization.form.label.name: "Nom" +organization.form.label.name.help: "Nom de votre l'organisation." +organization.form.label.siret: 'SIRET' +organization.form.label.siret.help: "Numéro de SIRET de l'organisation." +organization.form.label.website: 'Site web' +organization.form.label.website.help: "URL du site web de l'organisation. Exemple : https://example.org" +organization.form.label.logo: "Logo" +organization.form.label.logo.help: "Logo de l'organisation. Taille maximale : 1 Mo. Formats supportés : jpeg, png." +organization.form.label.description: 'Présentation' +organization.form.label.description.help: 'Décrivez en quelques lignes votre organisation. +Mise en forme autorisée : gras, paragraphe et liste au format Markdown.' +# See also "validators+intl-icu.en.yaml" file +# common.validator.help.image.error.not_valid_file: '' +################################################################################################################## user.change_password.head.title: 'Modifier votre mot de passe' user.change_password.page.title: 'Modifier votre mot de passe' user.change_password.success: 'Votre mot de passe a été modifié avec succès.' diff --git a/webapp/translations/validators+intl-icu.fr.yaml b/webapp/translations/validators+intl-icu.fr.yaml index 041ec03e54f7fb240416dfcc239a6856173d64bc..14ed0e614acc589be21311fb7b13be57a6f30d54 100644 --- a/webapp/translations/validators+intl-icu.fr.yaml +++ b/webapp/translations/validators+intl-icu.fr.yaml @@ -1,3 +1,4 @@ +common.validator.help.image.error.not_valid_file: "Le fichier envoyé n'est pas un fichier jpeg ou png." common.validator.help.password.error.password-must-match: 'Les champs du mot de passe doivent correspondre.' common.validator.help.password.error.new-password-must-match: 'Les champs du nouveau mot de passe doivent correspondre.' common.validator.help.email.error.not-valid: 'Veuillez saisir une adresse email valide.'