diff --git a/assets/js/app.js b/assets/js/app.js index b3c093cf3d62931ba62511440d7bcf8c4653c293..545f217523a507f47408443f017cac8de9c9e89d 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -22,14 +22,18 @@ $(document).ready(function() { $('.complex-choice-group').each(function(idx) { var check = $(this).find('.check input'); var comment = $(this).find('.comment input'); + var commentarea = $(this).find('.comment textarea'); // Disable comment on unchecked line comment.prop('disabled', !check.is(':checked')); + commentarea.prop('disabled', !check.is(':checked')); // Check | Uncheck : Disable comment on unchecked line check.on('change', function() { var comment = $(this).closest('.complex-choice-group').find('.comment input'); comment.prop('disabled', !check.is(':checked')); + var commentarea = $(this).closest('.complex-choice-group').find('.comment textarea'); + commentarea.prop('disabled', !check.is(':checked')); }) }); diff --git a/migrations/Version20240219091346.php b/migrations/Version20240219091346.php new file mode 100644 index 0000000000000000000000000000000000000000..a32a8a911f54639d84b313dd18ff4f676f852401 --- /dev/null +++ b/migrations/Version20240219091346.php @@ -0,0 +1,59 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20240219091346 extends AbstractMigration +{ + public function getDescription(): string + { + return 'Increase length of several fields to 500 characters'; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_access_control_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_tracability_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_saving_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_update_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_other_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY archival_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY encrypted_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY access_control_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY updating_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY backup_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY deletion_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY tracking_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY has_comment_comment VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY other_comment VARCHAR(500) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_access_control_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_tracability_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_saving_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_update_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_treatment MODIFY security_other_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY archival_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY encrypted_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY access_control_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY updating_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY backup_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY deletion_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY tracking_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY has_comment_comment VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE registry_tool MODIFY other_comment VARCHAR(255) DEFAULT NULL'); + } +} diff --git a/src/Domain/Registry/Controller/TreatmentController.php b/src/Domain/Registry/Controller/TreatmentController.php index 05182a2ab65bafa3135e9d0966473359f1cc92c4..0e402053c7e68db1d1b11001977df5e2fb945059 100644 --- a/src/Domain/Registry/Controller/TreatmentController.php +++ b/src/Domain/Registry/Controller/TreatmentController.php @@ -277,7 +277,7 @@ class TreatmentController extends CRUDController throw new AccessDeniedHttpException('You can\'t access to a collectivity treatment data'); } - /** @var UserModel\Collectivity|null $collectivity */ + /** @var Collectivity|null $collectivity */ $collectivity = $this->collectivityRepository->findOneById($collectivityId); if (null === $collectivity) { throw new NotFoundHttpException('Can\'t find collectivity for id ' . $collectivityId); diff --git a/src/Domain/Registry/Form/Type/Embeddable/ComplexChoiceAreaType.php b/src/Domain/Registry/Form/Type/Embeddable/ComplexChoiceAreaType.php new file mode 100644 index 0000000000000000000000000000000000000000..e6db4867c0063f9330167d6b4745a83a16c937b9 --- /dev/null +++ b/src/Domain/Registry/Form/Type/Embeddable/ComplexChoiceAreaType.php @@ -0,0 +1,77 @@ +<?php + +/** + * This file is part of the MADIS - RGPD Management application. + * + * @copyright Copyright (c) 2018-2019 Soluris - Solutions Numériques Territoriales Innovantes + * @author Donovan Bourlard <donovan@awkan.fr> + * + * This program 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +namespace App\Domain\Registry\Form\Type\Embeddable; + +use App\Domain\Registry\Model\Embeddable\ComplexChoice; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class ComplexChoiceAreaType extends AbstractType +{ + /** + * Build type form. + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('check', CheckboxType::class, [ + 'label' => false, + 'required' => false, + 'label_attr' => [ + 'removeColClass' => true, + ], + 'error_bubbling' => true, + ]) + ->add('comment', TextareaType::class, [ + 'label' => false, + 'required' => false, + 'attr' => [ + 'placeholder' => 'global.placeholder.precision', + 'maxlength' => 500, + 'rows' => 1, + 'class' => 'textareaheight', + ], + ]) + ; + } + + /** + * Provide type options. + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + 'data_class' => ComplexChoice::class, + 'validation_groups' => [ + 'default', + ], + ]) + ; + } +} diff --git a/src/Domain/Registry/Form/Type/ShelfLifeType.php b/src/Domain/Registry/Form/Type/ShelfLifeType.php index c885d514b9a64c04e340a11b146bfd2ee01bcba3..6df7866a701ff3ee19d416ea856e1807f0823a18 100644 --- a/src/Domain/Registry/Form/Type/ShelfLifeType.php +++ b/src/Domain/Registry/Form/Type/ShelfLifeType.php @@ -5,6 +5,7 @@ namespace App\Domain\Registry\Form\Type; use App\Domain\Registry\Model\ShelfLife; use Knp\DictionaryBundle\Form\Type\DictionaryType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -23,12 +24,14 @@ class ShelfLifeType extends AbstractType ], 'purify_html' => true, ]) - ->add('duration', TextType::class, [ + ->add('duration', TextareaType::class, [ 'label' => 'registry.treatment.label.shelflife_duration', 'required' => true, 'empty_data' => '', 'attr' => [ 'maxlength' => 500, + 'rows' => 1, + 'class' => 'textareaheight', ], 'purify_html' => true, ]) diff --git a/src/Domain/Registry/Form/Type/ToolType.php b/src/Domain/Registry/Form/Type/ToolType.php index 8179a74a7272247d98af8f76b52b431f635dfe11..dd86ebf311953349a993a965caff25ce7c0b6308 100644 --- a/src/Domain/Registry/Form/Type/ToolType.php +++ b/src/Domain/Registry/Form/Type/ToolType.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace App\Domain\Registry\Form\Type; -use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceType; +use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceAreaType; use App\Domain\Registry\Model\Contractor; use App\Domain\Registry\Model\Tool; use App\Domain\User\Model\Service; @@ -189,39 +189,39 @@ class ToolType extends AbstractType 'purify_html' => true, ]) - ->add('archival', ComplexChoiceType::class, [ + ->add('archival', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.archival', 'required' => false, ]) - ->add('encrypted', ComplexChoiceType::class, [ + ->add('encrypted', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.encrypted', 'required' => false, ]) - ->add('access_control', ComplexChoiceType::class, [ + ->add('access_control', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.access_control', 'required' => false, ]) - ->add('update', ComplexChoiceType::class, [ + ->add('update', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.update', 'required' => false, ]) - ->add('backup', ComplexChoiceType::class, [ + ->add('backup', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.backup', 'required' => false, ]) - ->add('deletion', ComplexChoiceType::class, [ + ->add('deletion', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.deletion', 'required' => false, ]) - ->add('tracking', ComplexChoiceType::class, [ + ->add('tracking', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.tracking', 'required' => false, ]) - ->add('has_comment', ComplexChoiceType::class, [ + ->add('has_comment', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.has_comment', 'required' => false, ]) - ->add('other', ComplexChoiceType::class, [ + ->add('other', ComplexChoiceAreaType::class, [ 'label' => 'registry.tool.label.other', 'required' => false, ]) diff --git a/src/Domain/Registry/Form/Type/TreatmentType.php b/src/Domain/Registry/Form/Type/TreatmentType.php index 2dcbd55719698f8a9a2d6f710a8f8c604565e704..290cda0acf2aea531d84511f6155a5e1d8c761d2 100644 --- a/src/Domain/Registry/Form/Type/TreatmentType.php +++ b/src/Domain/Registry/Form/Type/TreatmentType.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace App\Domain\Registry\Form\Type; +use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceAreaType; use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceType; use App\Domain\Registry\Model\Contractor; use App\Domain\Registry\Model\Tool; @@ -241,23 +242,23 @@ class TreatmentType extends AbstractType 'aria-label' => 'Sous-traitants', ], ]) - ->add('securityAccessControl', ComplexChoiceType::class, [ + ->add('securityAccessControl', ComplexChoiceAreaType::class, [ 'label' => 'registry.treatment.label.security_access_control', 'required' => false, ]) - ->add('securityTracability', ComplexChoiceType::class, [ + ->add('securityTracability', ComplexChoiceAreaType::class, [ 'label' => 'registry.treatment.label.security_tracability', 'required' => false, ]) - ->add('securitySaving', ComplexChoiceType::class, [ + ->add('securitySaving', ComplexChoiceAreaType::class, [ 'label' => 'registry.treatment.label.security_saving', 'required' => false, ]) - ->add('securityUpdate', ComplexChoiceType::class, [ + ->add('securityUpdate', ComplexChoiceAreaType::class, [ 'label' => 'registry.treatment.label.security_update', 'required' => false, ]) - ->add('securityOther', ComplexChoiceType::class, [ + ->add('securityOther', ComplexChoiceAreaType::class, [ 'label' => 'registry.treatment.label.security_other', 'required' => false, ]) diff --git a/templates/_Utils/form_theme.html.twig b/templates/_Utils/form_theme.html.twig index 72cfa7189d43a811324c61d4add8bb258adefbe7..a41e9d4cb84087666deb60bb3bc81d753cb042a0 100644 --- a/templates/_Utils/form_theme.html.twig +++ b/templates/_Utils/form_theme.html.twig @@ -36,6 +36,7 @@ </div> {%- endblock password_row %} +{# Complex choice type with input type text #} {% block complex_choice_row -%} {%- set widget_attr = {} -%} {%- if help is not empty -%} @@ -43,16 +44,34 @@ {%- endif -%} <div class="complex-choice-group form-group{% if not valid %} has-error{% endif %}{% if form.vars.attr.class is defined %} {{form.vars.attr.class}}{% endif %}"> <div class="check">{{ form_row(form.check) }}</div> - <div style="flex-grow: 1;"> + <div style="flex-grow: 1;max-width: calc(100% - 20px);"> {{ form_label(form, null, {'label_attr': {'for': form.check.vars.id, 'style': 'cursor: pointer;'}}) }} <div class="comment"> {{ form_widget(form.comment, widget_attr) }} - {{ form_errors(form.comment) }} {# -#} + {{ form_errors(form.comment) }} </div> </div> - </div> {# -#} + </div> {%- endblock complex_choice_row %} +{# Complex choice type with textarea type #} +{% block complex_choice_area_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + <div class="complex-choice-group form-group{% if not valid %} has-error{% endif %}{% if form.vars.attr.class is defined %} {{form.vars.attr.class}}{% endif %}"> + <div class="check">{{ form_row(form.check) }}</div> + <div style="flex-grow: 1;max-width: calc(100% - 20px);"> + {{ form_label(form, null, {'label_attr': {'for': form.check.vars.id, 'style': 'cursor: pointer;'}}) }} + <div class="comment"> + {{ form_widget(form.comment, widget_attr) }} + {{ form_errors(form.comment) }} + </div> + </div> + </div> +{%- endblock complex_choice_area_row %} + {% block answer_row -%} {%- set widget_attr = {} -%} {%- if help is not empty -%} diff --git a/tests/Domain/Registry/Form/Type/Embeddable/ComplexChoiceAreaTypeTest.php b/tests/Domain/Registry/Form/Type/Embeddable/ComplexChoiceAreaTypeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e2d5e83561c2b2dab304cc658a5768ad605ca9e5 --- /dev/null +++ b/tests/Domain/Registry/Form/Type/Embeddable/ComplexChoiceAreaTypeTest.php @@ -0,0 +1,66 @@ +<?php + +/** + * This file is part of the MADIS - RGPD Management application. + * + * @copyright Copyright (c) 2018-2019 Soluris - Solutions Numériques Territoriales Innovantes + * @author Donovan Bourlard <donovan@awkan.fr> + * + * This program 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +namespace App\Tests\Domain\Registry\Form\Type\Embeddable; + +use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceAreaType; +use App\Domain\Registry\Model\Embeddable\ComplexChoice; +use App\Tests\Utils\FormTypeHelper; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class ComplexChoiceAreaTypeTest extends FormTypeHelper +{ + public function testInstanceOf(): void + { + $this->assertInstanceOf(AbstractType::class, new ComplexChoiceAreaType()); + } + + public function testBuildForm(): void + { + $builder = [ + 'check' => CheckboxType::class, + 'comment' => TextareaType::class, + ]; + + (new ComplexChoiceAreaType())->buildForm($this->prophesizeBuilder($builder), []); + } + + public function testConfigureOptions(): void + { + $defaults = [ + 'data_class' => ComplexChoice::class, + 'validation_groups' => [ + 'default', + ], + ]; + + $resolverProphecy = $this->prophesize(OptionsResolver::class); + $resolverProphecy->setDefaults($defaults)->shouldBeCalled(); + + (new ComplexChoiceAreaType())->configureOptions($resolverProphecy->reveal()); + } +} diff --git a/tests/Domain/Registry/Form/Type/ToolTypeTest.php b/tests/Domain/Registry/Form/Type/ToolTypeTest.php index a36c85fbdaacaf23a5598f6f3a17ca93729fa379..75ff467c1a51d20c9becfd5a7f328dde0d5a3831 100644 --- a/tests/Domain/Registry/Form/Type/ToolTypeTest.php +++ b/tests/Domain/Registry/Form/Type/ToolTypeTest.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace App\Tests\Domain\Registry\Form\Type; -use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceType; +use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceAreaType; use App\Domain\Registry\Form\Type\ToolType; use App\Domain\Registry\Model\Tool; use App\Domain\User\Model\Collectivity; @@ -82,15 +82,15 @@ class ToolTypeTest extends FormTypeHelper 'country_type' => ChoiceType::class, 'country_name' => TextType::class, 'country_guarantees' => TextType::class, - 'archival' => ComplexChoiceType::class, - 'tracking' => ComplexChoiceType::class, - 'encrypted' => ComplexChoiceType::class, - 'access_control' => ComplexChoiceType::class, - 'update' => ComplexChoiceType::class, - 'backup' => ComplexChoiceType::class, - 'deletion' => ComplexChoiceType::class, - 'has_comment' => ComplexChoiceType::class, - 'other' => ComplexChoiceType::class, + 'archival' => ComplexChoiceAreaType::class, + 'tracking' => ComplexChoiceAreaType::class, + 'encrypted' => ComplexChoiceAreaType::class, + 'access_control' => ComplexChoiceAreaType::class, + 'update' => ComplexChoiceAreaType::class, + 'backup' => ComplexChoiceAreaType::class, + 'deletion' => ComplexChoiceAreaType::class, + 'has_comment' => ComplexChoiceAreaType::class, + 'other' => ComplexChoiceAreaType::class, 'updatedBy' => HiddenType::class, ]; diff --git a/tests/Domain/Registry/Form/Type/TreatmentTypeTest.php b/tests/Domain/Registry/Form/Type/TreatmentTypeTest.php index 31976ba379921de9021fa833ed1455bef734cf4f..29c70a2457ce96e1e7cf1d2e2594a2a9247093e1 100644 --- a/tests/Domain/Registry/Form/Type/TreatmentTypeTest.php +++ b/tests/Domain/Registry/Form/Type/TreatmentTypeTest.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace App\Tests\Domain\Registry\Form\Type; +use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceAreaType; use App\Domain\Registry\Form\Type\Embeddable\ComplexChoiceType; use App\Domain\Registry\Form\Type\TreatmentType; use App\Domain\Registry\Model\Treatment; @@ -112,11 +113,11 @@ class TreatmentTypeTest extends FormTypeHelper 'recipientCategory' => TextareaType::class, 'contractors' => EntityType::class, 'shelfLifes' => CollectionType::class, - 'securityAccessControl' => ComplexChoiceType::class, - 'securityTracability' => ComplexChoiceType::class, - 'securitySaving' => ComplexChoiceType::class, - 'securityUpdate' => ComplexChoiceType::class, - 'securityOther' => ComplexChoiceType::class, + 'securityAccessControl' => ComplexChoiceAreaType::class, + 'securityTracability' => ComplexChoiceAreaType::class, + 'securitySaving' => ComplexChoiceAreaType::class, + 'securityUpdate' => ComplexChoiceAreaType::class, + 'securityOther' => ComplexChoiceAreaType::class, 'systematicMonitoring' => CheckboxType::class, 'largeScaleCollection' => CheckboxType::class, 'vulnerablePeople' => CheckboxType::class, @@ -178,11 +179,11 @@ class TreatmentTypeTest extends FormTypeHelper 'recipientCategory' => TextareaType::class, 'contractors' => EntityType::class, 'shelfLifes' => CollectionType::class, - 'securityAccessControl' => ComplexChoiceType::class, - 'securityTracability' => ComplexChoiceType::class, - 'securitySaving' => ComplexChoiceType::class, - 'securityUpdate' => ComplexChoiceType::class, - 'securityOther' => ComplexChoiceType::class, + 'securityAccessControl' => ComplexChoiceAreaType::class, + 'securityTracability' => ComplexChoiceAreaType::class, + 'securitySaving' => ComplexChoiceAreaType::class, + 'securityUpdate' => ComplexChoiceAreaType::class, + 'securityOther' => ComplexChoiceAreaType::class, 'systematicMonitoring' => CheckboxType::class, 'largeScaleCollection' => CheckboxType::class, 'vulnerablePeople' => CheckboxType::class,