mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-16 19:54:58 +00:00
Compare commits
35 Commits
testing-20
...
v4.0.0
Author | SHA1 | Date | |
---|---|---|---|
3b82ab0e7f
|
|||
ccfae1dc75 | |||
8bc16dadb0 | |||
c4cc0baa8e | |||
aed114c75c | |||
e592b89c94 | |||
70e75adb7d | |||
6f7015b152 | |||
65dde1e6a0 | |||
d193c50922 | |||
840ef6eed8 | |||
b4bbb1a456 | |||
606435a6b3 | |||
404143f8a6 | |||
ec957a2fe3 | |||
8ed5e35f1a | |||
ec37676dab
|
|||
2d8cda30b9
|
|||
27d344c97d
|
|||
088e5692e2 | |||
298044bc82 | |||
ee4e223043 | |||
c53377ce8d | |||
0b580658de
|
|||
786c60a50d
|
|||
456f00566d
|
|||
a38116cca4
|
|||
|
9158e33854 | ||
|
af74f7860b | ||
c2842148c6 | |||
10e4c7da23 | |||
f680a35f49 | |||
7d0fe06651 | |||
fca10ada71
|
|||
a35d456308
|
@@ -1,6 +0,0 @@
|
||||
kind: DX
|
||||
body: Allow TranslatableMessage in flash messages
|
||||
time: 2025-04-01T14:47:28.814268801+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -1,44 +0,0 @@
|
||||
kind: DX
|
||||
body: |
|
||||
Rewrite exports to run them asynchronously
|
||||
|
||||
changelog: |
|
||||
- Add new methods to serialize data using the rector rule
|
||||
- Remove all references to the Request in filters, aggregators, filters. Actually, the most frequent occurence is `$security->getUser()`.
|
||||
- Refactor manually the initializeQuery method
|
||||
- Remove the injection of ExportManager into the constructor of each export element:
|
||||
|
||||
```diff
|
||||
|
||||
- class MyFormatter implements FormatterInterface
|
||||
+ class MyFormatter implements FormatterInterface, \Chill\MainBundle\Export\ExportManagerAwareInterface
|
||||
{
|
||||
+ use \Chill\MainBundle\Export\Helper\ExportManagerAwareTrait;
|
||||
|
||||
- public function __construct(private ExportManager $exportmanager) {}
|
||||
|
||||
public function MyMethod(): void
|
||||
{
|
||||
- $this->exportManager->getFilter('alias');
|
||||
+ $this->getExportManager()->getFilter('alias');
|
||||
}
|
||||
}
|
||||
```
|
||||
- configure messenger to handle export in a queue:
|
||||
|
||||
```diff
|
||||
# config/packages/messenger.yaml
|
||||
framework:
|
||||
messenger:
|
||||
routing:
|
||||
+ 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
time: 2025-04-07T12:10:10.682561327+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: Add columns or tables
|
@@ -1,6 +0,0 @@
|
||||
kind: DX
|
||||
body: Remove dead code for wopi-link module
|
||||
time: 2025-04-30T14:45:50.406111606+02:00
|
||||
custom:
|
||||
Issue: "352"
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: DX
|
||||
body: Replace library node-sass by sass, and upgrade bootstrap to version 5.3 (yarn upgrade / install is required)
|
||||
time: 2025-05-28T16:58:13.226870341+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Feature
|
||||
body: Allow the merge of two accompanying period works
|
||||
time: 2025-02-11T14:22:43.134106669+01:00
|
||||
custom:
|
||||
Issue: "359"
|
||||
SchemaChange: No schema change
|
@@ -1,7 +0,0 @@
|
||||
kind: Feature
|
||||
body: Add the document file name to the document title when a user upload a document,
|
||||
unless there is already a document title.
|
||||
time: 2025-04-24T14:22:11.800975422+02:00
|
||||
custom:
|
||||
Issue: "377"
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Feature
|
||||
body: Add desactivation date for social action and issue csv export
|
||||
time: 2025-05-20T09:56:28.108941934+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Feature
|
||||
body: Add Emoji and Fullscreen feature to ckeditor configuration
|
||||
time: 2025-05-23T13:33:41.645095128+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Feature
|
||||
body: Create editor which allow us to toggle between rich and simple text editor
|
||||
time: 2025-05-23T13:34:34.56795603+02:00
|
||||
custom:
|
||||
Issue: "321"
|
||||
SchemaChange: No schema change
|
@@ -1,7 +0,0 @@
|
||||
kind: Fixed
|
||||
body: trying to prevent bug of typeerror in doc-history + improved display of document
|
||||
history
|
||||
time: 2025-04-24T13:39:43.878468232+02:00
|
||||
custom:
|
||||
Issue: "376"
|
||||
SchemaChange: No schema change
|
@@ -1,7 +0,0 @@
|
||||
kind: Fixed
|
||||
body: Display previous participation in acc course work even if the person has left
|
||||
the acc course
|
||||
time: 2025-04-24T16:37:46.970203594+02:00
|
||||
custom:
|
||||
Issue: "381"
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Fixed
|
||||
body: Fix display of text in calendar events
|
||||
time: 2025-05-05T10:27:15.461493066+02:00
|
||||
custom:
|
||||
Issue: "372"
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Fixed
|
||||
body: Add missing translation for user_group.no_user_groups
|
||||
time: 2025-05-14T14:53:39.53927329+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Fixed
|
||||
body: Fix retrieve schema to form full tablename and construct sql statements correctly in Thirdparty merger.
|
||||
time: 2025-05-20T14:00:08.987229634+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Fixed
|
||||
body: Fix add missing translation
|
||||
time: 2025-05-20T14:04:33.612140549+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Fixed
|
||||
body: Fix the transfer of evaluations and documents during of accompanyingperiodwork
|
||||
time: 2025-05-20T16:44:29.093304653+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: UX
|
||||
body: Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page
|
||||
time: 2025-04-23T17:26:24.45777387+02:00
|
||||
custom:
|
||||
Issue: "374"
|
||||
SchemaChange: No schema change
|
22
.changes/v3.12.0.md
Normal file
22
.changes/v3.12.0.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## v3.12.0 - 2025-06-30
|
||||
### Feature
|
||||
* ([#377](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/377)) Add the document file name to the document title when a user upload a document, unless there is already a document title.
|
||||
* Add desactivation date for social action and issue csv export
|
||||
* Add Emoji and Fullscreen feature to ckeditor configuration
|
||||
* ([#321](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/321)) Create editor which allow us to toggle between rich and simple text editor
|
||||
* Do not remove workflow which are automatically canceled after staling for more than 30 days
|
||||
### Fixed
|
||||
* ([#376](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/376)) trying to prevent bug of typeerror in doc-history + improved display of document history
|
||||
* ([#381](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/381)) Display previous participation in acc course work even if the person has left the acc course
|
||||
* ([#372](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/372)) Fix display of text in calendar events
|
||||
* Add missing translation for user_group.no_user_groups
|
||||
* Fix admin entity edit actions for event admin entities and activity reason (category) entities
|
||||
* ([#392](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/392)) Allow null and cast as string to setContent method for NewsItem
|
||||
|
||||
* ([#393](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/393)) Doc Generation: the "dump only" method send the document as an email attachment.
|
||||
### DX
|
||||
* ([#352](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/352)) Remove dead code for wopi-link module
|
||||
* Replace library node-sass by sass, and upgrade bootstrap to version 5.3 (yarn upgrade / install is required)
|
||||
### UX
|
||||
* ([#374](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/374)) Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page
|
||||
* Improve labeling of fields in person resource creation form
|
3
.changes/v3.12.1.md
Normal file
3
.changes/v3.12.1.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v3.12.1 - 2025-06-30
|
||||
### Fixed
|
||||
* Fix loading of the list of documents
|
74
.changes/v4.0.0.md
Normal file
74
.changes/v4.0.0.md
Normal file
@@ -0,0 +1,74 @@
|
||||
## v4.0.0 - 2025-07-08
|
||||
### Feature
|
||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
|
||||
### Fixed
|
||||
* ([#390](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/390)) Display the list of participant in the results, even if there is only one participant and that the search result display the requestor
|
||||
* Fix admin entity edit actions for event admin entities and activity reason (category) entities
|
||||
* Fix translations for social action fields in admin form: results, goals, evaluations
|
||||
### DX
|
||||
* Rewrite exports to run them asynchronously
|
||||
|
||||
**Schema Change**: Add columns or tables
|
||||
* Allow TranslatableMessage in flash messages
|
||||
### UX
|
||||
* Improve labeling of fields in person resource creation form
|
||||
|
||||
|
||||
**Release notes**
|
||||
|
||||
- Add new methods to serialize data using the rector rule
|
||||
- Remove all references to the Request in filters, aggregators, filters. Actually, the most frequent occurence is `$security->getUser()`.
|
||||
- Refactor manually the initializeQuery method
|
||||
- Remove the injection of ExportManager into the constructor of each export element:
|
||||
|
||||
```diff
|
||||
|
||||
- class MyFormatter implements FormatterInterface
|
||||
+ class MyFormatter implements FormatterInterface, \Chill\MainBundle\Export\ExportManagerAwareInterface
|
||||
{
|
||||
+ use \Chill\MainBundle\Export\Helper\ExportManagerAwareTrait;
|
||||
|
||||
- public function __construct(private ExportManager $exportmanager) {}
|
||||
|
||||
public function MyMethod(): void
|
||||
{
|
||||
- $this->exportManager->getFilter('alias');
|
||||
+ $this->getExportManager()->getFilter('alias');
|
||||
}
|
||||
}
|
||||
```
|
||||
- configure messenger to handle export in a queue:
|
||||
|
||||
```diff
|
||||
# config/packages/messenger.yaml
|
||||
framework:
|
||||
messenger:
|
||||
routing:
|
||||
+ 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
||||
```
|
||||
|
||||
- add missing methods to exports, aggregators, filters, formatter:
|
||||
|
||||
```php
|
||||
public function normalizeFormData(array $formData): array;
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array;
|
||||
```
|
||||
|
||||
There are rector rules to generate those methods:
|
||||
|
||||
- `Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector`
|
||||
|
||||
See:
|
||||
|
||||
```php
|
||||
// upgrade chill exports
|
||||
$rectorConfig->rules([\Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class]);
|
||||
```
|
||||
|
||||
This rule will create most of the work necessary, but some manuals changes are still necessary:
|
||||
|
||||
- we must set manually the correct repository for method `denormalizeDoctrineEntity`;
|
||||
- when the form data contains some entities, and the form type is not one of EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class, then we must handle the normalization manually (using the `\Chill\MainBundle\Export\ExportDataNormalizerTrait`)
|
||||
|
||||
|
@@ -22,7 +22,7 @@ Chill is a comprehensive web application built as a set of Symfony bundles. It i
|
||||
- **Backend**: PHP 8.3+, Symfony 5.4
|
||||
- **Frontend**: JavaScript/TypeScript, Vue.js 3, Bootstrap 5
|
||||
- **Build Tools**: Webpack Encore, Yarn
|
||||
- **Database**: PostgreSQL with materialized views
|
||||
- **Database**: PostgreSQL with materialized views. We do not support other databases.
|
||||
- **Other Services**: Redis, AMQP (RabbitMQ), SMTP
|
||||
|
||||
## Project Structure
|
||||
@@ -149,6 +149,42 @@ Key configuration files:
|
||||
- `package.json`: JavaScript dependencies and scripts
|
||||
- `.env`: Default environment variables. Must usually not be updated: use `.env.local` instead.
|
||||
|
||||
### Database migrations
|
||||
|
||||
Each time a doctrine entity is created, we generate migration to adapt the database.
|
||||
|
||||
The migration are created using the command `symfony console doctrine:migrations:diff --no-interaction --namespace <namespace>`, where the namespace is the relevant namespace for migration. As this is a bash script, do not forget to quote the `\` (`\` must become `\\` in your command).
|
||||
|
||||
Each bundle has his own namespace for migration (always ask me to confirm that command, with a list of updated / created entities so that I can confirm you that it is ok):
|
||||
|
||||
- `Chill\Bundle\ActivityBundle` writes migrations to `Chill\Migrations\Activity`;
|
||||
- `Chill\Bundle\BudgetBundle` writes migrations to `Chill\Migrations\Budget`;
|
||||
- `Chill\Bundle\CustomFieldsBundle` writes migrations to `Chill\Migrations\CustomFields`;
|
||||
- `Chill\Bundle\DocGeneratorBundle` writes migrations to `Chill\Migrations\DocGenerator`;
|
||||
- `Chill\Bundle\DocStoreBundle` writes migrations to `Chill\Migrations\DocStore`;
|
||||
- `Chill\Bundle\EventBundle` writes migrations to `Chill\Migrations\Event`;
|
||||
- `Chill\Bundle\CalendarBundle` writes migrations to `Chill\Migrations\Calendar`;
|
||||
- `Chill\Bundle\FamilyMembersBundle` writes migrations to `Chill\Migrations\FamilyMembers`;
|
||||
- `Chill\Bundle\FranceTravailApiBundle` writes migrations to `Chill\Migrations\FranceTravailApi`;
|
||||
- `Chill\Bundle\JobBundle` writes migrations to `Chill\Migrations\Job`;
|
||||
- `Chill\Bundle\MainBundle` writes migrations to `Chill\Migrations\Main`;
|
||||
- `Chill\Bundle\PersonBundle` writes migrations to `Chill\Migrations\Person`;
|
||||
- `Chill\Bundle\ReportBundle` writes migrations to `Chill\Migrations\Report`;
|
||||
- `Chill\Bundle\TaskBundle` writes migrations to `Chill\Migrations\Task`;
|
||||
- `Chill\Bundle\ThirdPartyBundle` writes migrations to `Chill\Migrations\ThirdParty`;
|
||||
- `Chill\Bundle\TicketBundle` writes migrations to `Chill\Migrations\Ticket`;
|
||||
- `Chill\Bundle\WopiBundle` writes migrations to `Chill\Migrations\Wopi`;
|
||||
|
||||
Once created the, comment's classes should be removed and a description of the changes made to the entities should be added to the migrations, using the `getDescription` method. The migration should not be cleaned by any artificial intelligence, as modifying this migration is error prone.
|
||||
|
||||
### Guidelines related to code structure and requirements
|
||||
|
||||
#### Usage of clock
|
||||
|
||||
When we need to use a DateTime or DateTimeImmutable that need to express "now", we prefer the usage of
|
||||
`Symfony\Component\Clock\ClockInterface`, where possible. This is usually not possible in doctrine entities,
|
||||
where injection does not work when restoring an entity from database, but usually possible in services.
|
||||
|
||||
### Testing Information
|
||||
|
||||
The project uses PHPUnit for testing. Each bundle has its own test suite, and there's also a global test suite at the root level.
|
||||
@@ -218,7 +254,7 @@ class TicketTest extends TestCase
|
||||
|
||||
#### Test Database
|
||||
|
||||
For tests that require a database, the project uses an in-memory SQLite database by default. You can configure a different database for testing in the `.env.test` file.
|
||||
For tests that require a database, the project uses postgresql database filled by fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file.
|
||||
|
||||
### Code Quality Tools
|
||||
|
||||
|
122
CHANGELOG.md
122
CHANGELOG.md
@@ -6,6 +6,128 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v4.0.0 - 2025-07-08
|
||||
### Feature
|
||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
|
||||
### Fixed
|
||||
* ([#390](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/390)) Display the list of participant in the results, even if there is only one participant and that the search result display the requestor
|
||||
* Fix admin entity edit actions for event admin entities and activity reason (category) entities
|
||||
* Fix translations for social action fields in admin form: results, goals, evaluations
|
||||
### DX
|
||||
* Rewrite exports to run them asynchronously
|
||||
|
||||
**Schema Change**: Add columns or tables
|
||||
* Allow TranslatableMessage in flash messages
|
||||
### UX
|
||||
* Improve labeling of fields in person resource creation form
|
||||
|
||||
|
||||
**Release notes**
|
||||
|
||||
- Add new methods to serialize data using the rector rule
|
||||
- Remove all references to the Request in filters, aggregators, filters. Actually, the most frequent occurence is `$security->getUser()`.
|
||||
- Refactor manually the initializeQuery method
|
||||
- Remove the injection of ExportManager into the constructor of each export element:
|
||||
|
||||
```diff
|
||||
|
||||
- class MyFormatter implements FormatterInterface
|
||||
+ class MyFormatter implements FormatterInterface, \Chill\MainBundle\Export\ExportManagerAwareInterface
|
||||
{
|
||||
+ use \Chill\MainBundle\Export\Helper\ExportManagerAwareTrait;
|
||||
|
||||
- public function __construct(private ExportManager $exportmanager) {}
|
||||
|
||||
public function MyMethod(): void
|
||||
{
|
||||
- $this->exportManager->getFilter('alias');
|
||||
+ $this->getExportManager()->getFilter('alias');
|
||||
}
|
||||
}
|
||||
```
|
||||
- configure messenger to handle export in a queue:
|
||||
|
||||
```diff
|
||||
# config/packages/messenger.yaml
|
||||
framework:
|
||||
messenger:
|
||||
routing:
|
||||
+ 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
||||
```
|
||||
|
||||
- add missing methods to exports, aggregators, filters, formatter:
|
||||
|
||||
```php
|
||||
public function normalizeFormData(array $formData): array;
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array;
|
||||
```
|
||||
|
||||
There are rector rules to generate those methods:
|
||||
|
||||
- `Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector`
|
||||
|
||||
See:
|
||||
|
||||
```php
|
||||
// upgrade chill exports
|
||||
$rectorConfig->rules([\Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class]);
|
||||
```
|
||||
|
||||
This rule will create most of the work necessary, but some manuals changes are still necessary:
|
||||
|
||||
- we must set manually the correct repository for method `denormalizeDoctrineEntity`;
|
||||
- when the form data contains some entities, and the form type is not one of EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class, then we must handle the normalization manually (using the `\Chill\MainBundle\Export\ExportDataNormalizerTrait`)
|
||||
|
||||
|
||||
|
||||
## v3.12.1 - 2025-06-30
|
||||
### Fixed
|
||||
* Fix loading of the list of documents
|
||||
|
||||
## v3.12.0 - 2025-06-30
|
||||
### Feature
|
||||
* ([#377](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/377)) Add the document file name to the document title when a user upload a document, unless there is already a document title.
|
||||
* Add desactivation date for social action and issue csv export
|
||||
* Add Emoji and Fullscreen feature to ckeditor configuration
|
||||
* ([#321](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/321)) Create editor which allow us to toggle between rich and simple text editor
|
||||
* Do not remove workflow which are automatically canceled after staling for more than 30 days
|
||||
### Fixed
|
||||
* ([#376](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/376)) trying to prevent bug of typeerror in doc-history + improved display of document history
|
||||
* ([#381](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/381)) Display previous participation in acc course work even if the person has left the acc course
|
||||
* ([#372](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/372)) Fix display of text in calendar events
|
||||
* Add missing translation for user_group.no_user_groups
|
||||
* Fix admin entity edit actions for event admin entities and activity reason (category) entities
|
||||
* ([#392](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/392)) Allow null and cast as string to setContent method for NewsItem
|
||||
|
||||
* ([#393](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/393)) Doc Generation: the "dump only" method send the document as an email attachment.
|
||||
### DX
|
||||
* ([#352](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/352)) Remove dead code for wopi-link module
|
||||
* Replace library node-sass by sass, and upgrade bootstrap to version 5.3 (yarn upgrade / install is required)
|
||||
### UX
|
||||
* ([#374](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/374)) Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page
|
||||
* Improve labeling of fields in person resource creation form
|
||||
|
||||
## v3.11.0 - 2025-04-17
|
||||
### Feature
|
||||
* ([#365](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/365)) Add counters of actions and activities, with 2 boxes to (1) show the number of active actions on total actions and (2) show the number of activities in a accompanying period, and pills in menus for showing the number of active actions and the number of activities.
|
||||
* ([#364](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/364)) Added a second phone number "telephone2" to the thirdParty entity. Adapted twig templates and vuejs apps to handle this phone number
|
||||
|
||||
**Schema Change**: Add columns or tables
|
||||
* Signature: add a button to go directly to the signature zone, even if there is only one
|
||||
### Fixed
|
||||
* Fixed wrong translations in the on-the-fly for creation of thirdParty
|
||||
* Fixed update of phone number in on-the-fly edition of thirdParty
|
||||
* Fixed closing of modal when editing thirdParty in accompanying course works
|
||||
* Shorten the delay between two execution of AccompanyingPeriodStepChangeCronjob, to ensure at least one execution in a day
|
||||
* ([#102](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/102)) Fix display of title in document list
|
||||
* When cleaning the old stored object versions, do not throw an error if the stored object is not found on disk
|
||||
* Add consistent log prefix and key to logs when stale workflows are automatically canceled
|
||||
* ([#380](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/380)) Remove the "not null" validation constraint on recently added properties on HouseholdComposition
|
||||
|
||||
### DX
|
||||
* Add new chill-col style for displaying title and aside in a flex table
|
||||
|
||||
## v3.10.3 - 2025-03-18
|
||||
### DX
|
||||
* Eslint fixes
|
||||
|
@@ -11,6 +11,7 @@
|
||||
"@hotwired/stimulus": "^3.0.0",
|
||||
"@luminateone/eslint-baseline": "^1.0.9",
|
||||
"@symfony/stimulus-bridge": "^3.2.0",
|
||||
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
||||
"@symfony/webpack-encore": "^4.1.0",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
|
@@ -2154,11 +2154,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php
|
||||
|
||||
-
|
||||
message: "#^Instanceof between string and DateTimeInterface will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @var for property Chill\\\\MainBundle\\\\Export\\\\Helper\\\\ExportAddressHelper\\:\\:\\$unitNamesKeysCache contains unresolvable type\\.$#"
|
||||
count: 1
|
||||
|
@@ -37,9 +37,6 @@ return static function (RectorConfig $rectorConfig): void {
|
||||
$rectorConfig->rule(Rector\TypeDeclaration\Rector\Class_\MergeDateTimePropertyTypeDeclarationRector::class);
|
||||
$rectorConfig->rule(Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeDeclarationBasedOnParentClassMethodRector::class);
|
||||
|
||||
// upgrade chill exports
|
||||
$rectorConfig->rules([\Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class]);
|
||||
|
||||
// part of the symfony 54 rules
|
||||
$rectorConfig->rule(\Rector\Symfony\Symfony53\Rector\StaticPropertyFetch\KernelTestCaseContainerPropertyDeprecationRector::class);
|
||||
$rectorConfig->rule(\Rector\Symfony\Symfony60\Rector\MethodCall\GetHelperControllerToServiceRector::class);
|
||||
|
@@ -48,28 +48,6 @@ class ActivityReasonCategoryController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a form to edit an existing ActivityReasonCategory entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/edit', name: 'chill_activity_activityreasoncategory_edit')]
|
||||
public function editAction(mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
|
||||
$entity = $em->getRepository(ActivityReasonCategory::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.');
|
||||
}
|
||||
|
||||
$editForm = $this->createEditForm($entity);
|
||||
|
||||
return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [
|
||||
'entity' => $entity,
|
||||
'edit_form' => $editForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all ActivityReasonCategory entities.
|
||||
*/
|
||||
@@ -100,29 +78,10 @@ class ActivityReasonCategoryController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and displays a ActivityReasonCategory entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/show', name: 'chill_activity_activityreasoncategory_show')]
|
||||
public function showAction(mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
|
||||
$entity = $em->getRepository(ActivityReasonCategory::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.');
|
||||
}
|
||||
|
||||
return $this->render('@ChillActivity/ActivityReasonCategory/show.html.twig', [
|
||||
'entity' => $entity,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing ActivityReasonCategory entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/update', name: 'chill_activity_activityreasoncategory_update', methods: ['POST', 'PUT'])]
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/update', name: 'chill_activity_activityreasoncategory_update')]
|
||||
public function updateAction(Request $request, mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
@@ -139,7 +98,7 @@ class ActivityReasonCategoryController extends AbstractController
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_activity_activityreasoncategory_edit', ['id' => $id]);
|
||||
return $this->redirectToRoute('chill_activity_activityreasoncategory', ['id' => $id]);
|
||||
}
|
||||
|
||||
return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [
|
||||
@@ -178,7 +137,7 @@ class ActivityReasonCategoryController extends AbstractController
|
||||
{
|
||||
$form = $this->createForm(ActivityReasonCategoryType::class, $entity, [
|
||||
'action' => $this->generateUrl('chill_activity_activityreasoncategory_update', ['id' => $entity->getId()]),
|
||||
'method' => 'PUT',
|
||||
'method' => 'POST',
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Update']);
|
||||
|
@@ -17,7 +17,6 @@ use Chill\ActivityBundle\Repository\ActivityReasonRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* ActivityReason controller.
|
||||
@@ -50,28 +49,6 @@ class ActivityReasonController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a form to edit an existing ActivityReason entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/edit', name: 'chill_activity_activityreason_edit')]
|
||||
public function editAction(mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
|
||||
$entity = $em->getRepository(ActivityReason::class)->find($id);
|
||||
|
||||
if (null === $entity) {
|
||||
throw new NotFoundHttpException('Unable to find ActivityReason entity.');
|
||||
}
|
||||
|
||||
$editForm = $this->createEditForm($entity);
|
||||
|
||||
return $this->render('@ChillActivity/ActivityReason/edit.html.twig', [
|
||||
'entity' => $entity,
|
||||
'edit_form' => $editForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all ActivityReason entities.
|
||||
*/
|
||||
@@ -102,29 +79,10 @@ class ActivityReasonController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and displays a ActivityReason entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/show', name: 'chill_activity_activityreason_show')]
|
||||
public function showAction(mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
|
||||
$entity = $em->getRepository(ActivityReason::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find ActivityReason entity.');
|
||||
}
|
||||
|
||||
return $this->render('@ChillActivity/ActivityReason/show.html.twig', [
|
||||
'entity' => $entity,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing ActivityReason entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/update', name: 'chill_activity_activityreason_update', methods: ['POST', 'PUT'])]
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/update', name: 'chill_activity_activityreason_update')]
|
||||
public function updateAction(Request $request, mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
@@ -180,7 +138,7 @@ class ActivityReasonController extends AbstractController
|
||||
{
|
||||
$form = $this->createForm(ActivityReasonType::class, $entity, [
|
||||
'action' => $this->generateUrl('chill_activity_activityreason_update', ['id' => $entity->getId()]),
|
||||
'method' => 'PUT',
|
||||
'method' => 'POST',
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Update']);
|
||||
|
@@ -2,7 +2,7 @@ import "es6-promise/auto";
|
||||
import { createStore } from "vuex";
|
||||
import { postLocation } from "./api";
|
||||
import prepareLocations from "./store.locations.js";
|
||||
import {fetchResults, makeFetch} from "ChillMainAssets/lib/api/apiMethods";
|
||||
import { fetchResults, makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
|
||||
const debug = process.env.NODE_ENV !== "production";
|
||||
//console.log('window.activity', window.activity);
|
||||
@@ -369,7 +369,7 @@ const store = createStore({
|
||||
// console.log('works', works);
|
||||
commit("setAccompanyingPeriodWorks", works);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch works:', error);
|
||||
console.error("Failed to fetch works:", error);
|
||||
}
|
||||
},
|
||||
getWhoAmI({ commit }) {
|
||||
|
@@ -3,7 +3,7 @@
|
||||
{% block admin_content %}
|
||||
<h1>{{ 'ActivityReason list'|trans }}</h1>
|
||||
|
||||
<table class="records_list">
|
||||
<table class="table table-bordered border-dark align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'Name'|trans }}</th>
|
||||
@@ -29,10 +29,7 @@
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activityreason_show', { 'id': entity.id }) }}" class="btn btn-show" title="{{ 'show'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activityreason_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
<a href="{{ path('chill_activity_activityreason_update', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
{% block admin_content %}
|
||||
<h1>{{ 'ActivityReasonCategory list'|trans }}</h1>
|
||||
|
||||
<table class="records_list">
|
||||
<table class="table table-bordered border-dark align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'Name'|trans }}</th>
|
||||
@@ -23,10 +23,7 @@
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activityreasoncategory_show', { 'id': entity.id }) }}" class="btn btn-show" title="{{ 'show'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activityreasoncategory_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
<a href="{{ path('chill_activity_activityreasoncategory_update', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
|
@@ -22,6 +22,52 @@ use Symfony\Component\Security\Core\Role\Role;
|
||||
*/
|
||||
final class ActivityControllerTest extends WebTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getSecuredPagesUnauthenticated
|
||||
*/
|
||||
public function testAccessIsDeniedForUnauthenticated(mixed $url)
|
||||
{
|
||||
$client = $this->createClient();
|
||||
|
||||
$client->request('GET', $url);
|
||||
|
||||
$this->assertEquals(302, $client->getResponse()->getStatusCode());
|
||||
$this->assertTrue(
|
||||
$client->getResponse()->isRedirect('http://localhost/login'),
|
||||
sprintf('the page "%s" does not redirect to http://localhost/login', $url)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a client unauthenticated and.
|
||||
*/
|
||||
public function getSecuredPagesUnauthenticated()
|
||||
{
|
||||
self::bootKernel();
|
||||
$person = $this->getPersonFromFixtures();
|
||||
$activities = $this->getActivitiesForPerson($person);
|
||||
|
||||
return [
|
||||
[sprintf('fr/person/%d/activity/', $person->getId())],
|
||||
[sprintf('fr/person/%d/activity/new', $person->getId())],
|
||||
[sprintf('fr/person/%d/activity/%d/show', $person->getId(), $activities[0]->getId())],
|
||||
[sprintf('fr/person/%d/activity/%d/edit', $person->getId(), $activities[0]->getId())],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getSecuredPagesAuthenticated
|
||||
*
|
||||
* @param type $client
|
||||
* @param type $url
|
||||
*/
|
||||
public function testAccessIsDeniedForUnauthorized($client, $url)
|
||||
{
|
||||
$client->request('GET', $url);
|
||||
|
||||
$this->assertEquals(403, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function getSecuredPagesAuthenticated()
|
||||
{
|
||||
self::bootKernel();
|
||||
@@ -55,52 +101,6 @@ final class ActivityControllerTest extends WebTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a client unauthenticated and.
|
||||
*/
|
||||
public function getSecuredPagesUnauthenticated()
|
||||
{
|
||||
self::bootKernel();
|
||||
$person = $this->getPersonFromFixtures();
|
||||
$activities = $this->getActivitiesForPerson($person);
|
||||
|
||||
return [
|
||||
[sprintf('fr/person/%d/activity/', $person->getId())],
|
||||
[sprintf('fr/person/%d/activity/new', $person->getId())],
|
||||
[sprintf('fr/person/%d/activity/%d/show', $person->getId(), $activities[0]->getId())],
|
||||
[sprintf('fr/person/%d/activity/%d/edit', $person->getId(), $activities[0]->getId())],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getSecuredPagesUnauthenticated
|
||||
*/
|
||||
public function testAccessIsDeniedForUnauthenticated(mixed $url)
|
||||
{
|
||||
$client = $this->createClient();
|
||||
|
||||
$client->request('GET', $url);
|
||||
|
||||
$this->assertEquals(302, $client->getResponse()->getStatusCode());
|
||||
$this->assertTrue(
|
||||
$client->getResponse()->isRedirect('http://localhost/login'),
|
||||
sprintf('the page "%s" does not redirect to http://localhost/login', $url)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getSecuredPagesAuthenticated
|
||||
*
|
||||
* @param type $client
|
||||
* @param type $url
|
||||
*/
|
||||
public function testAccessIsDeniedForUnauthorized($client, $url)
|
||||
{
|
||||
$client->request('GET', $url);
|
||||
|
||||
$this->assertEquals(403, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testCompleteScenario()
|
||||
{
|
||||
// Create a new client to browse the application
|
||||
|
@@ -137,6 +137,64 @@ class ActivityACLAwareRepositoryTest extends KernelTestCase
|
||||
self::assertIsArray($actual);
|
||||
}
|
||||
|
||||
public function provideDataFindByAccompanyingPeriod(): iterable
|
||||
{
|
||||
$this->setUp();
|
||||
|
||||
if (null === $period = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('a')
|
||||
->from(AccompanyingPeriod::class, 'a')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getSingleResult()) {
|
||||
throw new \RuntimeException('no period found');
|
||||
}
|
||||
|
||||
if ([] === $types = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('t')
|
||||
->from(ActivityType::class, 't')
|
||||
->setMaxResults(2)
|
||||
->getQuery()
|
||||
->getResult()) {
|
||||
throw new \RuntimeException('no types');
|
||||
}
|
||||
|
||||
if ([] === $jobs = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('j')
|
||||
->from(UserJob::class, 'j')
|
||||
->setMaxResults(2)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
) {
|
||||
$job = new UserJob();
|
||||
$job->setLabel(['fr' => 'test']);
|
||||
$this->entityManager->persist($job);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
if (null === $user = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('u')
|
||||
->from(User::class, 'u')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getSingleResult()
|
||||
) {
|
||||
throw new \RuntimeException('no user found');
|
||||
}
|
||||
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], []];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['my_activities' => true]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['types' => $types]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['jobs' => $jobs]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataFindByPerson
|
||||
*/
|
||||
@@ -291,62 +349,4 @@ class ActivityACLAwareRepositoryTest extends KernelTestCase
|
||||
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]];
|
||||
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]];
|
||||
}
|
||||
|
||||
public function provideDataFindByAccompanyingPeriod(): iterable
|
||||
{
|
||||
$this->setUp();
|
||||
|
||||
if (null === $period = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('a')
|
||||
->from(AccompanyingPeriod::class, 'a')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getSingleResult()) {
|
||||
throw new \RuntimeException('no period found');
|
||||
}
|
||||
|
||||
if ([] === $types = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('t')
|
||||
->from(ActivityType::class, 't')
|
||||
->setMaxResults(2)
|
||||
->getQuery()
|
||||
->getResult()) {
|
||||
throw new \RuntimeException('no types');
|
||||
}
|
||||
|
||||
if ([] === $jobs = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('j')
|
||||
->from(UserJob::class, 'j')
|
||||
->setMaxResults(2)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
) {
|
||||
$job = new UserJob();
|
||||
$job->setLabel(['fr' => 'test']);
|
||||
$this->entityManager->persist($job);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
if (null === $user = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('u')
|
||||
->from(User::class, 'u')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getSingleResult()
|
||||
) {
|
||||
throw new \RuntimeException('no user found');
|
||||
}
|
||||
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], []];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['my_activities' => true]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['types' => $types]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['jobs' => $jobs]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]];
|
||||
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]];
|
||||
}
|
||||
}
|
||||
|
@@ -57,6 +57,46 @@ final class ActivityVoterTest extends KernelTestCase
|
||||
$this->prophet = new \Prophecy\Prophet();
|
||||
}
|
||||
|
||||
public function testNullUser()
|
||||
{
|
||||
$token = $this->prepareToken();
|
||||
$center = $this->prepareCenter(1, 'center');
|
||||
$person = $this->preparePerson($center);
|
||||
$scope = $this->prepareScope(1, 'default');
|
||||
$activity = $this->prepareActivity($scope, $person);
|
||||
|
||||
$this->assertEquals(
|
||||
VoterInterface::ACCESS_DENIED,
|
||||
$this->voter->vote($token, $activity, ['CHILL_ACTIVITY_SEE']),
|
||||
'assert that a null user is not allowed to see'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProvider_testVoteAction
|
||||
*
|
||||
* @param type $expectedResult
|
||||
* @param string $attribute
|
||||
* @param string $message
|
||||
*/
|
||||
public function testVoteAction(
|
||||
$expectedResult,
|
||||
User $user,
|
||||
Scope $scope,
|
||||
Center $center,
|
||||
$attribute,
|
||||
$message,
|
||||
) {
|
||||
$token = $this->prepareToken($user);
|
||||
$activity = $this->prepareActivity($scope, $this->preparePerson($center));
|
||||
|
||||
$this->assertEquals(
|
||||
$expectedResult,
|
||||
$this->voter->vote($token, $activity, [$attribute]),
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
public function dataProvider_testVoteAction()
|
||||
{
|
||||
$centerA = $this->prepareCenter(1, 'center A');
|
||||
@@ -110,46 +150,6 @@ final class ActivityVoterTest extends KernelTestCase
|
||||
];
|
||||
}
|
||||
|
||||
public function testNullUser()
|
||||
{
|
||||
$token = $this->prepareToken();
|
||||
$center = $this->prepareCenter(1, 'center');
|
||||
$person = $this->preparePerson($center);
|
||||
$scope = $this->prepareScope(1, 'default');
|
||||
$activity = $this->prepareActivity($scope, $person);
|
||||
|
||||
$this->assertEquals(
|
||||
VoterInterface::ACCESS_DENIED,
|
||||
$this->voter->vote($token, $activity, ['CHILL_ACTIVITY_SEE']),
|
||||
'assert that a null user is not allowed to see'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProvider_testVoteAction
|
||||
*
|
||||
* @param type $expectedResult
|
||||
* @param string $attribute
|
||||
* @param string $message
|
||||
*/
|
||||
public function testVoteAction(
|
||||
$expectedResult,
|
||||
User $user,
|
||||
Scope $scope,
|
||||
Center $center,
|
||||
$attribute,
|
||||
$message,
|
||||
) {
|
||||
$token = $this->prepareToken($user);
|
||||
$activity = $this->prepareActivity($scope, $this->preparePerson($center));
|
||||
|
||||
$this->assertEquals(
|
||||
$expectedResult,
|
||||
$this->voter->vote($token, $activity, [$attribute]),
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare a token interface with correct rights.
|
||||
*
|
||||
|
@@ -30,6 +30,18 @@ final class AsideActivityControllerTest extends WebTestCase
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateAsideActivityId
|
||||
*/
|
||||
public function testEditWithoutUsers(int $asideActivityId)
|
||||
{
|
||||
self::ensureKernelShutdown();
|
||||
$client = $this->getClientAuthenticated();
|
||||
$client->request('GET', "/fr/asideactivity/{$asideActivityId}/edit");
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public static function generateAsideActivityId(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
@@ -58,18 +70,6 @@ final class AsideActivityControllerTest extends WebTestCase
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateAsideActivityId
|
||||
*/
|
||||
public function testEditWithoutUsers(int $asideActivityId)
|
||||
{
|
||||
self::ensureKernelShutdown();
|
||||
$client = $this->getClientAuthenticated();
|
||||
$client->request('GET', "/fr/asideactivity/{$asideActivityId}/edit");
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testIndexWithoutUsers()
|
||||
{
|
||||
self::ensureKernelShutdown();
|
||||
|
@@ -42,6 +42,32 @@ final class CalendarControllerTest extends WebTestCase
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideAccompanyingPeriod
|
||||
*/
|
||||
public function testList(int $accompanyingPeriodId)
|
||||
{
|
||||
$this->client->request(
|
||||
Request::METHOD_GET,
|
||||
sprintf('/fr/calendar/calendar/by-period/%d', $accompanyingPeriodId)
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideAccompanyingPeriod
|
||||
*/
|
||||
public function testNew(int $accompanyingPeriodId)
|
||||
{
|
||||
$this->client->request(
|
||||
Request::METHOD_GET,
|
||||
sprintf('/fr/calendar/calendar/new?accompanying_period_id=%d', $accompanyingPeriodId)
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public static function provideAccompanyingPeriod(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
@@ -82,30 +108,4 @@ final class CalendarControllerTest extends WebTestCase
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideAccompanyingPeriod
|
||||
*/
|
||||
public function testList(int $accompanyingPeriodId)
|
||||
{
|
||||
$this->client->request(
|
||||
Request::METHOD_GET,
|
||||
sprintf('/fr/calendar/calendar/by-period/%d', $accompanyingPeriodId)
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideAccompanyingPeriod
|
||||
*/
|
||||
public function testNew(int $accompanyingPeriodId)
|
||||
{
|
||||
$this->client->request(
|
||||
Request::METHOD_GET,
|
||||
sprintf('/fr/calendar/calendar/new?accompanying_period_id=%d', $accompanyingPeriodId)
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
}
|
||||
|
@@ -45,20 +45,6 @@ class MSUserAbsenceReaderTest extends TestCase
|
||||
self::assertEquals($expected, $absenceReader->isUserAbsent($user), $message);
|
||||
}
|
||||
|
||||
public function testIsUserAbsentWithoutRemoteId(): void
|
||||
{
|
||||
$user = new User();
|
||||
$client = new MockHttpClient();
|
||||
|
||||
$mapUser = $this->prophesize(MapCalendarToUser::class);
|
||||
$mapUser->getUserId($user)->willReturn(null);
|
||||
$clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00'));
|
||||
|
||||
$absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock);
|
||||
|
||||
self::assertNull($absenceReader->isUserAbsent($user), 'when no user found, absence should be null');
|
||||
}
|
||||
|
||||
public static function provideDataTestUserAbsence(): iterable
|
||||
{
|
||||
// contains data that was retrieved from microsoft graph api on 2023-07-06
|
||||
@@ -173,4 +159,18 @@ class MSUserAbsenceReaderTest extends TestCase
|
||||
'User is absent: absence is always enabled',
|
||||
];
|
||||
}
|
||||
|
||||
public function testIsUserAbsentWithoutRemoteId(): void
|
||||
{
|
||||
$user = new User();
|
||||
$client = new MockHttpClient();
|
||||
|
||||
$mapUser = $this->prophesize(MapCalendarToUser::class);
|
||||
$mapUser->getUserId($user)->willReturn(null);
|
||||
$clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00'));
|
||||
|
||||
$absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock);
|
||||
|
||||
self::assertNull($absenceReader->isUserAbsent($user), 'when no user found, absence should be null');
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,24 @@ use PHPUnit\Framework\TestCase;
|
||||
*/
|
||||
final class DefaultRangeGeneratorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider generateData
|
||||
*/
|
||||
public function testGenerateRange(\DateTimeImmutable $date, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate)
|
||||
{
|
||||
$generator = new DefaultRangeGenerator();
|
||||
|
||||
['startDate' => $actualStartDate, 'endDate' => $actualEndDate] = $generator->generateRange($date);
|
||||
|
||||
if (null === $startDate) {
|
||||
$this->assertNull($actualStartDate);
|
||||
$this->assertNull($actualEndDate);
|
||||
} else {
|
||||
$this->assertEquals($startDate->format(\DateTimeImmutable::ATOM), $actualStartDate->format(\DateTimeImmutable::ATOM));
|
||||
$this->assertEquals($endDate->format(\DateTimeImmutable::ATOM), $actualEndDate->format(\DateTimeImmutable::ATOM));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* * Lundi => Envoi des rdv du mardi et mercredi.
|
||||
* * Mardi => Envoi des rdv du jeudi.
|
||||
@@ -79,22 +97,4 @@ final class DefaultRangeGeneratorTest extends TestCase
|
||||
null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateData
|
||||
*/
|
||||
public function testGenerateRange(\DateTimeImmutable $date, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate)
|
||||
{
|
||||
$generator = new DefaultRangeGenerator();
|
||||
|
||||
['startDate' => $actualStartDate, 'endDate' => $actualEndDate] = $generator->generateRange($date);
|
||||
|
||||
if (null === $startDate) {
|
||||
$this->assertNull($actualStartDate);
|
||||
$this->assertNull($actualEndDate);
|
||||
} else {
|
||||
$this->assertEquals($startDate->format(\DateTimeImmutable::ATOM), $actualStartDate->format(\DateTimeImmutable::ATOM));
|
||||
$this->assertEquals($endDate->format(\DateTimeImmutable::ATOM), $actualEndDate->format(\DateTimeImmutable::ATOM));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -49,80 +49,6 @@ final class CustomFieldsChoiceTest extends KernelTestCase
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* provide empty data in different possible representations.
|
||||
* Those data are supposed to be deserialized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function emptyDataProvider()
|
||||
{
|
||||
return [
|
||||
// 0
|
||||
[
|
||||
// signle
|
||||
'',
|
||||
],
|
||||
// 1
|
||||
[
|
||||
// single
|
||||
null,
|
||||
],
|
||||
// 2
|
||||
[
|
||||
// signle with allow other
|
||||
['_other' => 'something', '_choices' => ''],
|
||||
],
|
||||
// 3
|
||||
[
|
||||
// multiple
|
||||
[],
|
||||
],
|
||||
// 4
|
||||
[
|
||||
// multiple with allow other
|
||||
['_other' => 'something', '_choices' => []],
|
||||
],
|
||||
// 5
|
||||
[
|
||||
// multiple with allow other
|
||||
['_other' => '', '_choices' => []],
|
||||
],
|
||||
// 6
|
||||
[
|
||||
// empty
|
||||
['_other' => null, '_choices' => null],
|
||||
],
|
||||
// 7
|
||||
[
|
||||
// empty
|
||||
[null],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function serializedRepresentationDataProvider()
|
||||
{
|
||||
return [
|
||||
[
|
||||
// multiple => false, allow_other => false
|
||||
'my-value',
|
||||
],
|
||||
[
|
||||
// multiple => true, allow_ther => false
|
||||
['my-value'],
|
||||
],
|
||||
[
|
||||
// multiple => false, allow_other => true, current value not in other
|
||||
['_other' => '', '_choices' => 'my-value'],
|
||||
],
|
||||
[
|
||||
// multiple => true, allow_other => true, current value not in other
|
||||
['_other' => '', '_choices' => ['my-value']],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the representation of the data is deserialized to an array text
|
||||
* with an "allow_other" field.
|
||||
@@ -412,6 +338,58 @@ final class CustomFieldsChoiceTest extends KernelTestCase
|
||||
$this->assertTrue($isEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
* provide empty data in different possible representations.
|
||||
* Those data are supposed to be deserialized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function emptyDataProvider()
|
||||
{
|
||||
return [
|
||||
// 0
|
||||
[
|
||||
// signle
|
||||
'',
|
||||
],
|
||||
// 1
|
||||
[
|
||||
// single
|
||||
null,
|
||||
],
|
||||
// 2
|
||||
[
|
||||
// signle with allow other
|
||||
['_other' => 'something', '_choices' => ''],
|
||||
],
|
||||
// 3
|
||||
[
|
||||
// multiple
|
||||
[],
|
||||
],
|
||||
// 4
|
||||
[
|
||||
// multiple with allow other
|
||||
['_other' => 'something', '_choices' => []],
|
||||
],
|
||||
// 5
|
||||
[
|
||||
// multiple with allow other
|
||||
['_other' => '', '_choices' => []],
|
||||
],
|
||||
// 6
|
||||
[
|
||||
// empty
|
||||
['_other' => null, '_choices' => null],
|
||||
],
|
||||
// 7
|
||||
[
|
||||
// empty
|
||||
[null],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// ///////////////////////////////////////
|
||||
//
|
||||
// test function isEmptyValue
|
||||
@@ -435,6 +413,28 @@ final class CustomFieldsChoiceTest extends KernelTestCase
|
||||
$this->assertFalse($isEmpty);
|
||||
}
|
||||
|
||||
public static function serializedRepresentationDataProvider()
|
||||
{
|
||||
return [
|
||||
[
|
||||
// multiple => false, allow_other => false
|
||||
'my-value',
|
||||
],
|
||||
[
|
||||
// multiple => true, allow_ther => false
|
||||
['my-value'],
|
||||
],
|
||||
[
|
||||
// multiple => false, allow_other => true, current value not in other
|
||||
['_other' => '', '_choices' => 'my-value'],
|
||||
],
|
||||
[
|
||||
// multiple => true, allow_other => true, current value not in other
|
||||
['_other' => '', '_choices' => ['my-value']],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
|
@@ -58,6 +58,7 @@
|
||||
|
||||
<script>
|
||||
import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
|
||||
export default {
|
||||
name: "PickTemplate",
|
||||
@@ -113,6 +114,9 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
localizeString(str) {
|
||||
return localizeString(str);
|
||||
},
|
||||
clickGenerate(event, link) {
|
||||
if (!this.preventDefaultMoveToGenerate) {
|
||||
window.location.assign(link);
|
||||
|
@@ -1,7 +1,5 @@
|
||||
{{ 'docgen.data_dump_email.Dear'|trans }}
|
||||
|
||||
{{ 'docgen.data_dump_email.data_dump_ready_and_link'|trans }}
|
||||
{{ 'docgen.data_dump_email.data_dump_ready_and_attached'|trans }}
|
||||
|
||||
{{ link }}
|
||||
|
||||
{{ 'docgen.data_dump_email.link_valid_until'|trans({validity: validity}) }}
|
||||
{{ 'docgen.data_dump_email.filename'|trans({filename: filename}) }}
|
||||
|
@@ -11,13 +11,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Service\Messenger;
|
||||
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||
use Chill\DocGeneratorBundle\Service\Generator\Generator;
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
|
||||
use Chill\DocGeneratorBundle\Service\Generator\GeneratorException;
|
||||
use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
|
||||
use Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -37,15 +37,15 @@ class RequestGenerationHandler implements MessageHandlerInterface
|
||||
private const LOG_PREFIX = '[docgen message handler] ';
|
||||
|
||||
public function __construct(
|
||||
private readonly DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||
private readonly DocGeneratorTemplateRepositoryInterface $docGeneratorTemplateRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly Generator $generator,
|
||||
private readonly GeneratorInterface $generator,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly StoredObjectRepository $storedObjectRepository,
|
||||
private readonly StoredObjectRepositoryInterface $storedObjectRepository,
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
private readonly MailerInterface $mailer,
|
||||
private readonly TempUrlGeneratorInterface $tempUrlGenerator,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly StoredObjectManagerInterface $storedObjectManager,
|
||||
) {}
|
||||
|
||||
public function __invoke(RequestGenerationMessage $message)
|
||||
@@ -90,7 +90,7 @@ class RequestGenerationHandler implements MessageHandlerInterface
|
||||
|
||||
$this->sendDataDump($destinationStoredObject, $message);
|
||||
} else {
|
||||
$destinationStoredObject = $this->generator->generateDocFromTemplate(
|
||||
$this->generator->generateDocFromTemplate(
|
||||
$template,
|
||||
$message->getEntityId(),
|
||||
$message->getContextGenerationData(),
|
||||
@@ -122,19 +122,20 @@ class RequestGenerationHandler implements MessageHandlerInterface
|
||||
|
||||
private function sendDataDump(StoredObject $destinationStoredObject, RequestGenerationMessage $message): void
|
||||
{
|
||||
$url = $this->tempUrlGenerator->generate('GET', $destinationStoredObject->getFilename(), 3600);
|
||||
$parts = [];
|
||||
parse_str(parse_url($url->url)['query'], $parts);
|
||||
$validity = \DateTimeImmutable::createFromFormat('U', $parts['temp_url_expires']);
|
||||
// Get the content of the document
|
||||
$content = $this->storedObjectManager->read($destinationStoredObject);
|
||||
$filename = $destinationStoredObject->getFilename();
|
||||
$contentType = $destinationStoredObject->getType();
|
||||
|
||||
// Create the email with the document as an attachment
|
||||
$email = (new TemplatedEmail())
|
||||
->to($message->getSendResultToEmail())
|
||||
->textTemplate('@ChillDocGenerator/Email/send_data_dump_to_admin.txt.twig')
|
||||
->context([
|
||||
'link' => $url->url,
|
||||
'validity' => $validity,
|
||||
'filename' => $filename,
|
||||
])
|
||||
->subject($this->translator->trans('docgen.data_dump_email.subject'));
|
||||
->subject($this->translator->trans('docgen.data_dump_email.subject'))
|
||||
->attach($content, $filename, $contentType);
|
||||
|
||||
$this->mailer->send($email);
|
||||
}
|
||||
|
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Tests\Service\Messenger;
|
||||
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
|
||||
use Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface;
|
||||
use Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationHandler;
|
||||
use Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class RequestGenerationHandlerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testGenerationHappyScenario(): void
|
||||
{
|
||||
// Create entities
|
||||
$template = new DocGeneratorTemplate();
|
||||
$this->setPrivateProperty($template, 'id', 1);
|
||||
|
||||
$storedObject = new StoredObject();
|
||||
$this->setPrivateProperty($storedObject, 'id', 2);
|
||||
|
||||
$creator = new User();
|
||||
$creator->setEmail('test@example.com');
|
||||
$this->setPrivateProperty($creator, 'id', 3);
|
||||
|
||||
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||
$docGeneratorTemplateRepository->find(1)->willReturn($template);
|
||||
|
||||
$storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class);
|
||||
$storedObjectRepository->find(2)->willReturn($storedObject);
|
||||
|
||||
$userRepository = $this->prophesize(UserRepositoryInterface::class);
|
||||
$userRepository->find(3)->willReturn($creator);
|
||||
|
||||
// Create a mock for the Query object
|
||||
$query = $this->prophesize(Query::class);
|
||||
$query->setParameter('id', 2)->willReturn($query->reveal());
|
||||
$query->execute()->shouldBeCalled();
|
||||
|
||||
// Create a mock for the EntityManager
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->createQuery(Argument::containingString('UPDATE'))->willReturn($query->reveal());
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$generator = $this->prophesize(GeneratorInterface::class);
|
||||
$generator->generateDocFromTemplate(
|
||||
$template,
|
||||
123, // entityId
|
||||
['key' => 'value'], // contextGenerationData
|
||||
$storedObject,
|
||||
$creator
|
||||
)
|
||||
->willReturn($storedObject)->shouldBeCalled();
|
||||
|
||||
$logger = new NullLogger();
|
||||
|
||||
$mailer = $this->prophesize(MailerInterface::class);
|
||||
|
||||
$translator = $this->prophesize(TranslatorInterface::class);
|
||||
|
||||
$storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class);
|
||||
|
||||
// Create handler
|
||||
$handler = new RequestGenerationHandler(
|
||||
$docGeneratorTemplateRepository->reveal(),
|
||||
$entityManager->reveal(),
|
||||
$generator->reveal(),
|
||||
$logger,
|
||||
$storedObjectRepository->reveal(),
|
||||
$userRepository->reveal(),
|
||||
$mailer->reveal(),
|
||||
$translator->reveal(),
|
||||
$storedObjectManager->reveal()
|
||||
);
|
||||
|
||||
// Create message
|
||||
$message = new RequestGenerationMessage(
|
||||
$creator,
|
||||
$template,
|
||||
123, // entityId
|
||||
$storedObject,
|
||||
['key' => 'value'], // contextGenerationData
|
||||
false, // isTest
|
||||
null, // sendResultToEmail
|
||||
false // dumpOnly
|
||||
);
|
||||
|
||||
// Invoke handler
|
||||
$handler->__invoke($message);
|
||||
|
||||
// Assertions
|
||||
// The assertions are handled by the shouldBeCalled() expectations on the mocks
|
||||
$this->assertTrue(true); // Just to have an assertion in the test
|
||||
}
|
||||
|
||||
private function setPrivateProperty(object $object, string $propertyName, $value): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($object);
|
||||
$property = $reflection->getProperty($propertyName);
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($object, $value);
|
||||
}
|
||||
}
|
@@ -31,6 +31,36 @@ final class DocGenEncoderTest extends TestCase
|
||||
$this->encoder = new DocGenEncoder();
|
||||
}
|
||||
|
||||
public function testEmbeddedLoopsThrowsException()
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
|
||||
$data = [
|
||||
'data' => [
|
||||
['item' => 'one'],
|
||||
[
|
||||
'embedded' => [
|
||||
[
|
||||
['subitem' => 'two'],
|
||||
['subitem' => 'three'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->encoder->encode($data, 'docgen');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateEncodeData
|
||||
*/
|
||||
public function testEncode(mixed $expected, mixed $data, string $msg)
|
||||
{
|
||||
$generated = $this->encoder->encode($data, 'docgen');
|
||||
$this->assertEquals($expected, $generated, $msg);
|
||||
}
|
||||
|
||||
public static function generateEncodeData()
|
||||
{
|
||||
yield [['tests' => 'ok'], ['tests' => 'ok'], 'A simple test with a simple array'];
|
||||
@@ -93,34 +123,4 @@ final class DocGenEncoderTest extends TestCase
|
||||
'a longer list, with near real data inside and embedded associative arrays',
|
||||
];
|
||||
}
|
||||
|
||||
public function testEmbeddedLoopsThrowsException()
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
|
||||
$data = [
|
||||
'data' => [
|
||||
['item' => 'one'],
|
||||
[
|
||||
'embedded' => [
|
||||
[
|
||||
['subitem' => 'two'],
|
||||
['subitem' => 'three'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->encoder->encode($data, 'docgen');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateEncodeData
|
||||
*/
|
||||
public function testEncode(mixed $expected, mixed $data, string $msg)
|
||||
{
|
||||
$generated = $this->encoder->encode($data, 'docgen');
|
||||
$this->assertEquals($expected, $generated, $msg);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,2 @@
|
||||
docgen:
|
||||
data_dump_email:
|
||||
link_valid_until: >-
|
||||
Ce lien est valide jusqu'au {validity, date, full}, {validity, time, medium}
|
||||
# No ICU messages needed for data_dump_email anymore
|
||||
|
@@ -34,8 +34,10 @@ docgen:
|
||||
data_dump_email:
|
||||
subject: Contenu des données de génération de document disponible
|
||||
Dear: Cher
|
||||
data_dump_ready_and_link: >-
|
||||
Le contenu des données est disponible. Vous pouvez le télécharger à l'aide du lien suivant:
|
||||
data_dump_ready_and_attached: >-
|
||||
Le contenu des données est disponible. Vous le trouverez en pièce jointe à cet email.
|
||||
filename: >-
|
||||
Nom du fichier: %filename%
|
||||
|
||||
|
||||
|
||||
|
@@ -85,6 +85,69 @@ class TempUrlLocalStorageGeneratorTest extends TestCase
|
||||
self::assertEquals($expected, $urlGenerator->validateSignature($signature, $method, $objectName, $expiration), $message);
|
||||
}
|
||||
|
||||
public static function generateValidateSignatureData(): iterable
|
||||
{
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'GET',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
true,
|
||||
'Valid signature, not expired',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('HEAD', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'HEAD',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
true,
|
||||
'Valid signature, not expired',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180).'A',
|
||||
'GET',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
false,
|
||||
'Invalid signature',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'GET',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration + 1)),
|
||||
false,
|
||||
'Signature expired',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'GET',
|
||||
$object_name.'____',
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
false,
|
||||
'Invalid object name',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'POST',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
false,
|
||||
'Wrong method',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateValidateSignaturePostData
|
||||
*/
|
||||
@@ -164,69 +227,6 @@ class TempUrlLocalStorageGeneratorTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
public static function generateValidateSignatureData(): iterable
|
||||
{
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'GET',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
true,
|
||||
'Valid signature, not expired',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('HEAD', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'HEAD',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
true,
|
||||
'Valid signature, not expired',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180).'A',
|
||||
'GET',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
false,
|
||||
'Invalid signature',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'GET',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration + 1)),
|
||||
false,
|
||||
'Signature expired',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'GET',
|
||||
$object_name.'____',
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
false,
|
||||
'Invalid object name',
|
||||
];
|
||||
|
||||
yield [
|
||||
TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180),
|
||||
'POST',
|
||||
$object_name,
|
||||
$expiration,
|
||||
\DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
|
||||
false,
|
||||
'Wrong method',
|
||||
];
|
||||
}
|
||||
|
||||
private function buildGenerator(?UrlGeneratorInterface $urlGenerator = null, ?ClockInterface $clock = null): TempUrlLocalStorageGenerator
|
||||
{
|
||||
return new TempUrlLocalStorageGenerator(
|
||||
|
@@ -31,6 +31,20 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
*/
|
||||
final class StoredObjectManagerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getDataProviderForRead
|
||||
*/
|
||||
public function testRead(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null)
|
||||
{
|
||||
if (null !== $exceptionClass) {
|
||||
$this->expectException($exceptionClass);
|
||||
}
|
||||
|
||||
$storedObjectManager = $this->getSubject($storedObject, $encodedContent);
|
||||
|
||||
self::assertEquals($clearContent, $storedObjectManager->read($storedObject));
|
||||
}
|
||||
|
||||
public static function getDataProviderForRead(): \Generator
|
||||
{
|
||||
/* HAPPY SCENARIO */
|
||||
@@ -96,6 +110,40 @@ final class StoredObjectManagerTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getDataProviderForWrite
|
||||
*/
|
||||
public function testWrite(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null, ?int $errorCode = null)
|
||||
{
|
||||
if (null !== $exceptionClass) {
|
||||
$this->expectException($exceptionClass);
|
||||
}
|
||||
|
||||
$previousVersion = $storedObject->getCurrentVersion();
|
||||
$previousFilename = $previousVersion->getFilename();
|
||||
|
||||
$client = new MockHttpClient(function ($method, $url, $options) use ($encodedContent, $previousFilename, $errorCode) {
|
||||
self::assertEquals('PUT', $method);
|
||||
self::assertStringStartsWith('https://example.com/', $url);
|
||||
self::assertStringNotContainsString($previousFilename, $url, 'test that the PUT operation is not performed on the same file');
|
||||
self::assertArrayHasKey('body', $options);
|
||||
self::assertEquals($encodedContent, $options['body']);
|
||||
|
||||
if (-1 === $errorCode) {
|
||||
throw new TransportException();
|
||||
}
|
||||
|
||||
return new MockResponse('', ['http_code' => $errorCode ?? 201]);
|
||||
});
|
||||
|
||||
$storedObjectManager = new StoredObjectManager($client, $this->getTempUrlGenerator($storedObject));
|
||||
|
||||
$newVersion = $storedObjectManager->write($storedObject, $clearContent);
|
||||
|
||||
self::assertNotSame($previousVersion, $newVersion);
|
||||
self::assertSame($storedObject->getCurrentVersion(), $newVersion);
|
||||
}
|
||||
|
||||
public static function getDataProviderForWrite(): \Generator
|
||||
{
|
||||
/* HAPPY SCENARIO */
|
||||
@@ -150,54 +198,6 @@ final class StoredObjectManagerTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getDataProviderForRead
|
||||
*/
|
||||
public function testRead(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null)
|
||||
{
|
||||
if (null !== $exceptionClass) {
|
||||
$this->expectException($exceptionClass);
|
||||
}
|
||||
|
||||
$storedObjectManager = $this->getSubject($storedObject, $encodedContent);
|
||||
|
||||
self::assertEquals($clearContent, $storedObjectManager->read($storedObject));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getDataProviderForWrite
|
||||
*/
|
||||
public function testWrite(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null, ?int $errorCode = null)
|
||||
{
|
||||
if (null !== $exceptionClass) {
|
||||
$this->expectException($exceptionClass);
|
||||
}
|
||||
|
||||
$previousVersion = $storedObject->getCurrentVersion();
|
||||
$previousFilename = $previousVersion->getFilename();
|
||||
|
||||
$client = new MockHttpClient(function ($method, $url, $options) use ($encodedContent, $previousFilename, $errorCode) {
|
||||
self::assertEquals('PUT', $method);
|
||||
self::assertStringStartsWith('https://example.com/', $url);
|
||||
self::assertStringNotContainsString($previousFilename, $url, 'test that the PUT operation is not performed on the same file');
|
||||
self::assertArrayHasKey('body', $options);
|
||||
self::assertEquals($encodedContent, $options['body']);
|
||||
|
||||
if (-1 === $errorCode) {
|
||||
throw new TransportException();
|
||||
}
|
||||
|
||||
return new MockResponse('', ['http_code' => $errorCode ?? 201]);
|
||||
});
|
||||
|
||||
$storedObjectManager = new StoredObjectManager($client, $this->getTempUrlGenerator($storedObject));
|
||||
|
||||
$newVersion = $storedObjectManager->write($storedObject, $clearContent);
|
||||
|
||||
self::assertNotSame($previousVersion, $newVersion);
|
||||
self::assertSame($storedObject->getCurrentVersion(), $newVersion);
|
||||
}
|
||||
|
||||
public function testDelete(): void
|
||||
{
|
||||
$storedObject = new StoredObject();
|
||||
|
@@ -82,6 +82,38 @@ class TempUrlOpenstackGeneratorTest extends KernelTestCase
|
||||
self::assertEquals($expected, $signedUrl);
|
||||
}
|
||||
|
||||
public static function dataProviderGenerate(): iterable
|
||||
{
|
||||
$now = \DateTimeImmutable::createFromFormat('U', '1702041743');
|
||||
$expireDelay = 1800;
|
||||
$baseUrls = [
|
||||
'https://objectstore.example/v1/my_account/container/',
|
||||
'https://objectstore.example/v1/my_account/container',
|
||||
];
|
||||
$objectName = 'object';
|
||||
$method = 'GET';
|
||||
$key = 'MYKEY';
|
||||
|
||||
$signedUrl = new SignedUrl(
|
||||
'GET',
|
||||
'https://objectstore.example/v1/my_account/container/object?temp_url_sig=0aeef353a5f6e22d125c76c6ad8c644a59b222ba1b13eaeb56bf3d04e28b081d11dfcb36601ab3aa7b623d79e1ef03017071bbc842fb7b34afec2baff895bf80&temp_url_expires=1702043543',
|
||||
\DateTimeImmutable::createFromFormat('U', '1702043543'),
|
||||
$objectName
|
||||
);
|
||||
|
||||
foreach ($baseUrls as $baseUrl) {
|
||||
yield [
|
||||
$baseUrl,
|
||||
$now,
|
||||
$key,
|
||||
$method,
|
||||
$objectName,
|
||||
$expireDelay,
|
||||
$signedUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderGeneratePost
|
||||
*/
|
||||
@@ -125,38 +157,6 @@ class TempUrlOpenstackGeneratorTest extends KernelTestCase
|
||||
self::assertGreaterThanOrEqual(20, strlen($signedUrl->prefix));
|
||||
}
|
||||
|
||||
public static function dataProviderGenerate(): iterable
|
||||
{
|
||||
$now = \DateTimeImmutable::createFromFormat('U', '1702041743');
|
||||
$expireDelay = 1800;
|
||||
$baseUrls = [
|
||||
'https://objectstore.example/v1/my_account/container/',
|
||||
'https://objectstore.example/v1/my_account/container',
|
||||
];
|
||||
$objectName = 'object';
|
||||
$method = 'GET';
|
||||
$key = 'MYKEY';
|
||||
|
||||
$signedUrl = new SignedUrl(
|
||||
'GET',
|
||||
'https://objectstore.example/v1/my_account/container/object?temp_url_sig=0aeef353a5f6e22d125c76c6ad8c644a59b222ba1b13eaeb56bf3d04e28b081d11dfcb36601ab3aa7b623d79e1ef03017071bbc842fb7b34afec2baff895bf80&temp_url_expires=1702043543',
|
||||
\DateTimeImmutable::createFromFormat('U', '1702043543'),
|
||||
$objectName
|
||||
);
|
||||
|
||||
foreach ($baseUrls as $baseUrl) {
|
||||
yield [
|
||||
$baseUrl,
|
||||
$now,
|
||||
$key,
|
||||
$method,
|
||||
$objectName,
|
||||
$expireDelay,
|
||||
$signedUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public static function dataProviderGeneratePost(): iterable
|
||||
{
|
||||
$now = \DateTimeImmutable::createFromFormat('U', '1702041743');
|
||||
|
@@ -61,6 +61,55 @@ class StoredObjectContentToLocalStorageControllerTest extends TestCase
|
||||
$controller->contentOperate($request);
|
||||
}
|
||||
|
||||
public static function generateOperateContentWithExceptionDataProvider(): iterable
|
||||
{
|
||||
yield [
|
||||
new Request(['object_name' => '', 'sig' => '', 'exp' => 0]),
|
||||
BadRequestHttpException::class,
|
||||
'Object name parameter is missing',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => 0]),
|
||||
BadRequestHttpException::class,
|
||||
'Expiration is not set or equal to zero',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
|
||||
BadRequestHttpException::class,
|
||||
'Signature is not set or is a blank string',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
|
||||
AccessDeniedHttpException::class,
|
||||
'Invalid signature',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
];
|
||||
|
||||
|
||||
yield [
|
||||
new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
|
||||
NotFoundHttpException::class,
|
||||
'Object does not exists on disk',
|
||||
false,
|
||||
'',
|
||||
true,
|
||||
];
|
||||
}
|
||||
|
||||
public function testOperateContentGetHappyScenario(): void
|
||||
{
|
||||
$objectName = 'testABC';
|
||||
@@ -286,53 +335,4 @@ class StoredObjectContentToLocalStorageControllerTest extends TestCase
|
||||
'Filename does not start with signed prefix',
|
||||
];
|
||||
}
|
||||
|
||||
public static function generateOperateContentWithExceptionDataProvider(): iterable
|
||||
{
|
||||
yield [
|
||||
new Request(['object_name' => '', 'sig' => '', 'exp' => 0]),
|
||||
BadRequestHttpException::class,
|
||||
'Object name parameter is missing',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => 0]),
|
||||
BadRequestHttpException::class,
|
||||
'Expiration is not set or equal to zero',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
|
||||
BadRequestHttpException::class,
|
||||
'Signature is not set or is a blank string',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
|
||||
AccessDeniedHttpException::class,
|
||||
'Invalid signature',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
];
|
||||
|
||||
|
||||
yield [
|
||||
new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
|
||||
NotFoundHttpException::class,
|
||||
'Object does not exists on disk',
|
||||
false,
|
||||
'',
|
||||
true,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -136,63 +136,6 @@ class WebdavControllerTest extends KernelTestCase
|
||||
self::assertXmlStringEqualsXmlString($expectedXmlResponse, $response->getContent(), $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateDataPropfindDirectory
|
||||
*/
|
||||
public function testPropfindDirectory(string $requestContent, int $expectedStatusCode, string $expectedXmlResponse, string $message): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
|
||||
$request = new Request([], [], [], [], [], [], $requestContent);
|
||||
$request->setMethod('PROPFIND');
|
||||
$request->headers->add(['Depth' => '0']);
|
||||
$response = $controller->propfindDirectory($this->buildDocument(), '1234', $request);
|
||||
|
||||
self::assertEquals($expectedStatusCode, $response->getStatusCode());
|
||||
self::assertContains('content-type', $response->headers->keys());
|
||||
self::assertStringContainsString('text/xml', $response->headers->get('content-type'));
|
||||
self::assertTrue((new \DOMDocument())->loadXML($response->getContent()), $message.' test that the xml response is a valid xml');
|
||||
self::assertXmlStringEqualsXmlString($expectedXmlResponse, $response->getContent(), $message);
|
||||
}
|
||||
|
||||
public function testHeadDocument(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$response = $controller->headDocument($this->buildDocument());
|
||||
|
||||
self::assertEquals(200, $response->getStatusCode());
|
||||
self::assertContains('content-length', $response->headers->keys());
|
||||
self::assertContains('content-type', $response->headers->keys());
|
||||
self::assertContains('etag', $response->headers->keys());
|
||||
self::assertEquals('ab56b4d92b40713acc5af89985d4b786', $response->headers->get('etag'));
|
||||
self::assertEquals('application/vnd.oasis.opendocument.text', $response->headers->get('content-type'));
|
||||
self::assertEquals(5, $response->headers->get('content-length'));
|
||||
}
|
||||
|
||||
public function testPutDocument(): void
|
||||
{
|
||||
$document = $this->buildDocument();
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$storedObjectManager = $this->createMock(StoredObjectManagerInterface::class);
|
||||
|
||||
// entity manager must be flushed
|
||||
$entityManager->expects($this->once())
|
||||
->method('flush');
|
||||
|
||||
// object must be written by StoredObjectManager
|
||||
$storedObjectManager->expects($this->once())
|
||||
->method('write')
|
||||
->with($this->identicalTo($document), $this->identicalTo('1234'));
|
||||
|
||||
$controller = $this->buildController($entityManager, $storedObjectManager);
|
||||
|
||||
$request = new Request(content: '1234');
|
||||
$response = $controller->putDocument($document, $request);
|
||||
|
||||
self::assertEquals(204, $response->getStatusCode());
|
||||
self::assertEquals('', $response->getContent());
|
||||
}
|
||||
|
||||
public static function generateDataPropfindDocument(): iterable
|
||||
{
|
||||
$content =
|
||||
@@ -347,6 +290,25 @@ class WebdavControllerTest extends KernelTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateDataPropfindDirectory
|
||||
*/
|
||||
public function testPropfindDirectory(string $requestContent, int $expectedStatusCode, string $expectedXmlResponse, string $message): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
|
||||
$request = new Request([], [], [], [], [], [], $requestContent);
|
||||
$request->setMethod('PROPFIND');
|
||||
$request->headers->add(['Depth' => '0']);
|
||||
$response = $controller->propfindDirectory($this->buildDocument(), '1234', $request);
|
||||
|
||||
self::assertEquals($expectedStatusCode, $response->getStatusCode());
|
||||
self::assertContains('content-type', $response->headers->keys());
|
||||
self::assertStringContainsString('text/xml', $response->headers->get('content-type'));
|
||||
self::assertTrue((new \DOMDocument())->loadXML($response->getContent()), $message.' test that the xml response is a valid xml');
|
||||
self::assertXmlStringEqualsXmlString($expectedXmlResponse, $response->getContent(), $message);
|
||||
}
|
||||
|
||||
public static function generateDataPropfindDirectory(): iterable
|
||||
{
|
||||
yield [
|
||||
@@ -414,6 +376,44 @@ class WebdavControllerTest extends KernelTestCase
|
||||
'test creatableContentsInfo',
|
||||
];
|
||||
}
|
||||
|
||||
public function testHeadDocument(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$response = $controller->headDocument($this->buildDocument());
|
||||
|
||||
self::assertEquals(200, $response->getStatusCode());
|
||||
self::assertContains('content-length', $response->headers->keys());
|
||||
self::assertContains('content-type', $response->headers->keys());
|
||||
self::assertContains('etag', $response->headers->keys());
|
||||
self::assertEquals('ab56b4d92b40713acc5af89985d4b786', $response->headers->get('etag'));
|
||||
self::assertEquals('application/vnd.oasis.opendocument.text', $response->headers->get('content-type'));
|
||||
self::assertEquals(5, $response->headers->get('content-length'));
|
||||
}
|
||||
|
||||
public function testPutDocument(): void
|
||||
{
|
||||
$document = $this->buildDocument();
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$storedObjectManager = $this->createMock(StoredObjectManagerInterface::class);
|
||||
|
||||
// entity manager must be flushed
|
||||
$entityManager->expects($this->once())
|
||||
->method('flush');
|
||||
|
||||
// object must be written by StoredObjectManager
|
||||
$storedObjectManager->expects($this->once())
|
||||
->method('write')
|
||||
->with($this->identicalTo($document), $this->identicalTo('1234'));
|
||||
|
||||
$controller = $this->buildController($entityManager, $storedObjectManager);
|
||||
|
||||
$request = new Request(content: '1234');
|
||||
$response = $controller->putDocument($document, $request);
|
||||
|
||||
self::assertEquals(204, $response->getStatusCode());
|
||||
self::assertEquals('', $response->getContent());
|
||||
}
|
||||
}
|
||||
|
||||
class MockedStoredObjectManager implements StoredObjectManagerInterface
|
||||
|
@@ -87,6 +87,16 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase
|
||||
self::assertIsInt($nb, 'test that the query could be executed');
|
||||
}
|
||||
|
||||
public static function provideDataBuildFetchQueryForPerson(): iterable
|
||||
{
|
||||
yield [null, null, null];
|
||||
yield [new \DateTimeImmutable('1 year ago'), null, null];
|
||||
yield [null, new \DateTimeImmutable('1 year ago'), null];
|
||||
yield [new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), null];
|
||||
yield [null, null, 'test'];
|
||||
yield [new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), 'test'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDateForFetchQueryForAccompanyingPeriod
|
||||
*/
|
||||
@@ -142,14 +152,4 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase
|
||||
yield [$period, null, null, 'test'];
|
||||
yield [$period, new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), 'test'];
|
||||
}
|
||||
|
||||
public static function provideDataBuildFetchQueryForPerson(): iterable
|
||||
{
|
||||
yield [null, null, null];
|
||||
yield [new \DateTimeImmutable('1 year ago'), null, null];
|
||||
yield [null, new \DateTimeImmutable('1 year ago'), null];
|
||||
yield [new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), null];
|
||||
yield [null, null, 'test'];
|
||||
yield [new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), 'test'];
|
||||
}
|
||||
}
|
||||
|
@@ -50,19 +50,6 @@ class StoredObjectVoterTest extends TestCase
|
||||
self::assertEquals($expected, $voter->vote($token, $subject, [$attribute]));
|
||||
}
|
||||
|
||||
private function buildStoredObjectVoter(bool $supportsIsCalled, bool $supports, bool $voteOnAttribute): StoredObjectVoterInterface
|
||||
{
|
||||
$storedObjectVoter = $this->createMock(StoredObjectVoterInterface::class);
|
||||
$storedObjectVoter->expects($supportsIsCalled ? $this->once() : $this->never())->method('supports')
|
||||
->with(self::isInstanceOf(StoredObjectRoleEnum::class), $this->isInstanceOf(StoredObject::class))
|
||||
->willReturn($supports);
|
||||
$storedObjectVoter->expects($supportsIsCalled && $supports ? $this->once() : $this->never())->method('voteOnAttribute')
|
||||
->with(self::isInstanceOf(StoredObjectRoleEnum::class), $this->isInstanceOf(StoredObject::class), $this->isInstanceOf(TokenInterface::class))
|
||||
->willReturn($voteOnAttribute);
|
||||
|
||||
return $storedObjectVoter;
|
||||
}
|
||||
|
||||
public static function provideDataVote(): iterable
|
||||
{
|
||||
yield [
|
||||
@@ -120,4 +107,17 @@ class StoredObjectVoterTest extends TestCase
|
||||
VoterInterface::ACCESS_GRANTED,
|
||||
];
|
||||
}
|
||||
|
||||
private function buildStoredObjectVoter(bool $supportsIsCalled, bool $supports, bool $voteOnAttribute): StoredObjectVoterInterface
|
||||
{
|
||||
$storedObjectVoter = $this->createMock(StoredObjectVoterInterface::class);
|
||||
$storedObjectVoter->expects($supportsIsCalled ? $this->once() : $this->never())->method('supports')
|
||||
->with(self::isInstanceOf(StoredObjectRoleEnum::class), $this->isInstanceOf(StoredObject::class))
|
||||
->willReturn($supports);
|
||||
$storedObjectVoter->expects($supportsIsCalled && $supports ? $this->once() : $this->never())->method('voteOnAttribute')
|
||||
->with(self::isInstanceOf(StoredObjectRoleEnum::class), $this->isInstanceOf(StoredObject::class), $this->isInstanceOf(TokenInterface::class))
|
||||
->willReturn($voteOnAttribute);
|
||||
|
||||
return $storedObjectVoter;
|
||||
}
|
||||
}
|
||||
|
@@ -40,29 +40,6 @@ class RemoveOldVersionCronJobTest extends KernelTestCase
|
||||
self::assertEquals($expected, $cronJob->canRun($cronJobExecution));
|
||||
}
|
||||
|
||||
public function testRun(): void
|
||||
{
|
||||
// we create a clock in the future. This led us a chance to having stored object to delete
|
||||
$clock = new MockClock(new \DateTimeImmutable('2024-01-01 00:00:00', new \DateTimeZone('+00:00')));
|
||||
$repository = $this->createMock(StoredObjectVersionRepository::class);
|
||||
$repository->expects($this->once())
|
||||
->method('findIdsByVersionsOlderThanDateAndNotLastVersionAndNotPointInTime')
|
||||
->with(new \DateTime('2023-10-03 00:00:00', new \DateTimeZone('+00:00')))
|
||||
->willReturnCallback(function ($arg) {
|
||||
yield 1;
|
||||
yield 3;
|
||||
yield 2;
|
||||
})
|
||||
;
|
||||
|
||||
$cronJob = new RemoveOldVersionCronJob($clock, $this->buildMessageBus(true), $repository);
|
||||
|
||||
$results = $cronJob->run([]);
|
||||
|
||||
self::assertArrayHasKey('last-deleted-stored-object-version-id', $results);
|
||||
self::assertIsInt($results['last-deleted-stored-object-version-id']);
|
||||
}
|
||||
|
||||
public static function buildTestCanRunData(): iterable
|
||||
{
|
||||
yield [
|
||||
@@ -86,6 +63,29 @@ class RemoveOldVersionCronJobTest extends KernelTestCase
|
||||
];
|
||||
}
|
||||
|
||||
public function testRun(): void
|
||||
{
|
||||
// we create a clock in the future. This led us a chance to having stored object to delete
|
||||
$clock = new MockClock(new \DateTimeImmutable('2024-01-01 00:00:00', new \DateTimeZone('+00:00')));
|
||||
$repository = $this->createMock(StoredObjectVersionRepository::class);
|
||||
$repository->expects($this->once())
|
||||
->method('findIdsByVersionsOlderThanDateAndNotLastVersionAndNotPointInTime')
|
||||
->with(new \DateTime('2023-10-03 00:00:00', new \DateTimeZone('+00:00')))
|
||||
->willReturnCallback(function ($arg) {
|
||||
yield 1;
|
||||
yield 3;
|
||||
yield 2;
|
||||
})
|
||||
;
|
||||
|
||||
$cronJob = new RemoveOldVersionCronJob($clock, $this->buildMessageBus(true), $repository);
|
||||
|
||||
$results = $cronJob->run([]);
|
||||
|
||||
self::assertArrayHasKey('last-deleted-stored-object-version-id', $results);
|
||||
self::assertIsInt($results['last-deleted-stored-object-version-id']);
|
||||
}
|
||||
|
||||
private function buildMessageBus(bool $expectDistpatchAtLeastOnce = false): MessageBusInterface
|
||||
{
|
||||
$messageBus = $this->createMock(MessageBusInterface::class);
|
||||
|
@@ -48,30 +48,6 @@ class EventTypeController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a EventType entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/event_type/{id}/delete', name: 'chill_eventtype_admin_delete', methods: ['POST', 'DELETE'])]
|
||||
public function deleteAction(Request $request, mixed $id)
|
||||
{
|
||||
$form = $this->createDeleteForm($id);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->managerRegistry->getManager();
|
||||
$entity = $em->getRepository(EventType::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find EventType entity.');
|
||||
}
|
||||
|
||||
$em->remove($entity);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('chill_eventtype_admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a form to edit an existing EventType entity.
|
||||
*/
|
||||
@@ -87,12 +63,10 @@ class EventTypeController extends AbstractController
|
||||
}
|
||||
|
||||
$editForm = $this->createEditForm($entity);
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
|
||||
return $this->render('@ChillEvent/EventType/edit.html.twig', [
|
||||
'entity' => $entity,
|
||||
'edit_form' => $editForm->createView(),
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -126,28 +100,6 @@ class EventTypeController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and displays a EventType entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/event_type/{id}/show', name: 'chill_eventtype_admin_show')]
|
||||
public function showAction(mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
|
||||
$entity = $em->getRepository(EventType::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find EventType entity.');
|
||||
}
|
||||
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
|
||||
return $this->render('@ChillEvent/EventType/show.html.twig', [
|
||||
'entity' => $entity,
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing EventType entity.
|
||||
*/
|
||||
@@ -162,7 +114,6 @@ class EventTypeController extends AbstractController
|
||||
throw $this->createNotFoundException('Unable to find EventType entity.');
|
||||
}
|
||||
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
$editForm = $this->createEditForm($entity);
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
@@ -175,7 +126,6 @@ class EventTypeController extends AbstractController
|
||||
return $this->render('@ChillEvent/EventType/edit.html.twig', [
|
||||
'entity' => $entity,
|
||||
'edit_form' => $editForm->createView(),
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -198,22 +148,6 @@ class EventTypeController extends AbstractController
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form to delete a EventType entity by id.
|
||||
*
|
||||
* @return \Symfony\Component\Form\FormInterface The form
|
||||
*/
|
||||
private function createDeleteForm(mixed $id)
|
||||
{
|
||||
return $this->createFormBuilder()
|
||||
->setAction($this->generateUrl(
|
||||
'chill_eventtype_admin_delete',
|
||||
['id' => $id]
|
||||
))
|
||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||
->getForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form to edit a EventType entity.
|
||||
*
|
||||
@@ -228,7 +162,7 @@ class EventTypeController extends AbstractController
|
||||
'chill_eventtype_admin_update',
|
||||
['id' => $entity->getId()]
|
||||
),
|
||||
'method' => 'PUT',
|
||||
'method' => 'POST',
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Update']);
|
||||
|
@@ -48,30 +48,6 @@ class RoleController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a Role entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/role/{id}/delete', name: 'chill_event_admin_role_delete', methods: ['POST', 'DELETE'])]
|
||||
public function deleteAction(Request $request, mixed $id)
|
||||
{
|
||||
$form = $this->createDeleteForm($id);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->managerRegistry->getManager();
|
||||
$entity = $em->getRepository(Role::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find Role entity.');
|
||||
}
|
||||
|
||||
$em->remove($entity);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('chill_event_admin_role');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a form to edit an existing Role entity.
|
||||
*/
|
||||
@@ -87,12 +63,10 @@ class RoleController extends AbstractController
|
||||
}
|
||||
|
||||
$editForm = $this->createEditForm($entity);
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
|
||||
return $this->render('@ChillEvent/Role/edit.html.twig', [
|
||||
'entity' => $entity,
|
||||
'edit_form' => $editForm->createView(),
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -126,28 +100,6 @@ class RoleController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and displays a Role entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/role/{id}/show', name: 'chill_event_admin_role_show')]
|
||||
public function showAction(mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
|
||||
$entity = $em->getRepository(Role::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find Role entity.');
|
||||
}
|
||||
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
|
||||
return $this->render('@ChillEvent/Role/show.html.twig', [
|
||||
'entity' => $entity,
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing Role entity.
|
||||
*/
|
||||
@@ -162,7 +114,6 @@ class RoleController extends AbstractController
|
||||
throw $this->createNotFoundException('Unable to find Role entity.');
|
||||
}
|
||||
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
$editForm = $this->createEditForm($entity);
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
@@ -175,7 +126,6 @@ class RoleController extends AbstractController
|
||||
return $this->render('@ChillEvent/Role/edit.html.twig', [
|
||||
'entity' => $entity,
|
||||
'edit_form' => $editForm->createView(),
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -198,20 +148,6 @@ class RoleController extends AbstractController
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form to delete a Role entity by id.
|
||||
*
|
||||
* @return \Symfony\Component\Form\FormInterface The form
|
||||
*/
|
||||
private function createDeleteForm(mixed $id)
|
||||
{
|
||||
return $this->createFormBuilder()
|
||||
->setAction($this->generateUrl('chill_event_admin_role_delete', ['id' => $id]))
|
||||
->setMethod('DELETE')
|
||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||
->getForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form to edit a Role entity.
|
||||
*
|
||||
@@ -226,7 +162,7 @@ class RoleController extends AbstractController
|
||||
'chill_event_admin_role_update',
|
||||
['id' => $entity->getId()]
|
||||
),
|
||||
'method' => 'PUT',
|
||||
'method' => 'POST',
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Update']);
|
||||
|
@@ -48,30 +48,6 @@ class StatusController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a Status entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/status/{id}/delete', name: 'chill_event_admin_status_delete', methods: ['POST', 'DELETE'])]
|
||||
public function deleteAction(Request $request, mixed $id)
|
||||
{
|
||||
$form = $this->createDeleteForm($id);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->managerRegistry->getManager();
|
||||
$entity = $em->getRepository(Status::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find Status entity.');
|
||||
}
|
||||
|
||||
$em->remove($entity);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('chill_event_admin_status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a form to edit an existing Status entity.
|
||||
*/
|
||||
@@ -87,12 +63,10 @@ class StatusController extends AbstractController
|
||||
}
|
||||
|
||||
$editForm = $this->createEditForm($entity);
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
|
||||
return $this->render('@ChillEvent/Status/edit.html.twig', [
|
||||
'entity' => $entity,
|
||||
'edit_form' => $editForm->createView(),
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -126,28 +100,6 @@ class StatusController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and displays a Status entity.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/status/{id}/show', name: 'chill_event_admin_status_show')]
|
||||
public function showAction(mixed $id)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
|
||||
$entity = $em->getRepository(Status::class)->find($id);
|
||||
|
||||
if (!$entity) {
|
||||
throw $this->createNotFoundException('Unable to find Status entity.');
|
||||
}
|
||||
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
|
||||
return $this->render('@ChillEvent/Status/show.html.twig', [
|
||||
'entity' => $entity,
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing Status entity.
|
||||
*/
|
||||
@@ -162,7 +114,6 @@ class StatusController extends AbstractController
|
||||
throw $this->createNotFoundException('Unable to find Status entity.');
|
||||
}
|
||||
|
||||
$deleteForm = $this->createDeleteForm($id);
|
||||
$editForm = $this->createEditForm($entity);
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
@@ -175,7 +126,6 @@ class StatusController extends AbstractController
|
||||
return $this->render('@ChillEvent/Status/edit.html.twig', [
|
||||
'entity' => $entity,
|
||||
'edit_form' => $editForm->createView(),
|
||||
'delete_form' => $deleteForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -198,19 +148,6 @@ class StatusController extends AbstractController
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form to delete a Status entity by id.
|
||||
*
|
||||
* @return \Symfony\Component\Form\FormInterface The form
|
||||
*/
|
||||
private function createDeleteForm(mixed $id)
|
||||
{
|
||||
return $this->createFormBuilder()
|
||||
->setAction($this->generateUrl('chill_event_admin_status_delete', ['id' => $id]))
|
||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||
->getForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form to edit a Status entity.
|
||||
*
|
||||
@@ -222,7 +159,7 @@ class StatusController extends AbstractController
|
||||
{
|
||||
$form = $this->createForm(StatusType::class, $entity, [
|
||||
'action' => $this->generateUrl('chill_event_admin_status_update', ['id' => $entity->getId()]),
|
||||
'method' => 'PUT',
|
||||
'method' => 'POST',
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Update']);
|
||||
|
@@ -8,7 +8,7 @@
|
||||
{{ form_row(edit_form.name) }}
|
||||
{{ form_row(edit_form.active) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_eventtype_admin') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
|
@@ -16,14 +16,11 @@
|
||||
<tbody>
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td><a href="{{ path('chill_eventtype_admin_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
|
||||
<td>{{ entity.id }}</a></td>
|
||||
<td>{{ entity.name|localize_translatable_string }}</td>
|
||||
<td>{{ entity.active }}</td>
|
||||
<td><i class="fa {% if entity.active %}fa-check-square-o{% else %}fa-square-o{% endif %}"></i></td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_eventtype_admin_show', { 'id': entity.id }) }}" class="btn btn-show" title="{{ 'show'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_eventtype_admin_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
|
@@ -8,7 +8,7 @@
|
||||
{{ form_row(form.name) }}
|
||||
{{ form_row(form.active) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_eventtype_admin') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
|
@@ -21,17 +21,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_eventtype_admin') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_eventtype_admin_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'Edit'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_start(delete_form) }}
|
||||
{{ form_row(delete_form.submit, { 'attr': { 'class' : 'btn btn-delete' }}) }}
|
||||
{{ form_end(delete_form) }}
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
@@ -8,12 +8,12 @@
|
||||
{{ form_row(edit_form.type) }}
|
||||
{{ form_row(edit_form.active) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_event_admin_role') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_row(edit_form.submit, { 'attr': { 'class' : 'btn btn-edit' }}) }}
|
||||
{{ form_row(edit_form.submit, { 'attr': { 'class' : 'btn btn-update' }}) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@@ -17,15 +17,12 @@
|
||||
<tbody>
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td><a href="{{ path('chill_event_admin_role_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
|
||||
<td>{{ entity.id }}</a></td>
|
||||
<td>{{ entity.name|localize_translatable_string }}</td>
|
||||
<td>{{ entity.type.name|localize_translatable_string }}</td>
|
||||
<td>{{ entity.active }}</td>
|
||||
<td><i class="fa {% if entity.active %}fa-check-square-o{% else %}fa-square-o{% endif %}"></i></td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_event_admin_role_show', { 'id': entity.id }) }}" class="btn btn-show" title="{{ 'show'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_event_admin_role_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
{{ form_row(form.type) }}
|
||||
{{ form_row(form.active) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_event_admin_role') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
|
@@ -25,17 +25,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_event_admin_role') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_event_admin_role_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'Edit'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_start(delete_form) }}
|
||||
{{ form_row(delete_form.submit, { 'attr': { 'class' : 'btn btn-delete' }}) }}
|
||||
{{ form_end(delete_form) }}
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
@@ -9,7 +9,7 @@
|
||||
{{ form_row(edit_form.type) }}
|
||||
{{ form_row(edit_form.active) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_event_admin_status') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
|
@@ -17,15 +17,12 @@
|
||||
<tbody>
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td><a href="{{ path('chill_event_admin_status_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
|
||||
<td>{{ entity.id }}</a></td>
|
||||
<td>{{ entity.name|localize_translatable_string }}</td>
|
||||
<td>{{ entity.type.name|localize_translatable_string }}</td>
|
||||
<td>{{ entity.active }}</td>
|
||||
<td><i class="fa {% if entity.active %}fa-check-square-o{% else %}fa-square-o{% endif %}"></i></td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_event_admin_status_show', { 'id': entity.id }) }}" class="btn btn-show" title="{{ 'show'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_event_admin_status_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
{{ form_row(form.type) }}
|
||||
{{ form_row(form.active) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_event_admin_status') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
|
@@ -25,17 +25,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_event_admin_status') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_event_admin_status_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'Edit'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_start(delete_form) }}
|
||||
{{ form_row(delete_form.submit, { 'attr': { 'class' : 'btn btn-delete' }}) }}
|
||||
{{ form_end(delete_form) }}
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
@@ -149,7 +149,7 @@ class ExportController extends AbstractController
|
||||
->createNamedBuilder(
|
||||
'',
|
||||
FormType::class,
|
||||
$defaultFormData,
|
||||
'centers' === $step ? ['centers' => $defaultFormData] : $defaultFormData,
|
||||
[
|
||||
'method' => $isGenerate ? Request::METHOD_GET : Request::METHOD_POST,
|
||||
'csrf_protection' => !$isGenerate,
|
||||
@@ -352,7 +352,7 @@ class ExportController extends AbstractController
|
||||
$formCenters->submit($dataCenters);
|
||||
$dataAsCollection = $formCenters->getData()['centers'];
|
||||
$centers = $dataAsCollection['centers'];
|
||||
$regroupments = $dataAsCollection['regroupments'];
|
||||
$regroupments = $dataAsCollection['regroupments'] ?? [];
|
||||
$dataCenters = [
|
||||
'centers' => $centers instanceof Collection ? $centers->toArray() : $centers,
|
||||
'regroupments' => $regroupments instanceof Collection ? $regroupments->toArray() : $regroupments,
|
||||
@@ -377,7 +377,7 @@ class ExportController extends AbstractController
|
||||
}
|
||||
|
||||
return [
|
||||
'centers' => ['centers' => $dataCenters['centers'], 'regroupments' => $dataCenters['regroupments'] ?? []],
|
||||
'centers' => ['centers' => $dataCenters['centers'], 'regroupments' => $dataCenters['regroupments']],
|
||||
'export' => $dataExport['export']['export'] ?? [],
|
||||
'filters' => $dataExport['export']['filters'] ?? [],
|
||||
'aggregators' => $dataExport['export']['aggregators'] ?? [],
|
||||
@@ -404,7 +404,12 @@ class ExportController extends AbstractController
|
||||
/** @var ExportManager $exportManager */
|
||||
$exportManager = $this->exportManager;
|
||||
|
||||
$form = $this->createCreateFormExport($alias, 'centers', [], $savedExport);
|
||||
$form = $this->createCreateFormExport(
|
||||
$alias,
|
||||
'centers',
|
||||
$this->exportFormHelper->getDefaultData('centers', $export, []),
|
||||
$savedExport
|
||||
);
|
||||
|
||||
if (Request::METHOD_POST === $request->getMethod()) {
|
||||
$form->handleRequest($request);
|
||||
|
@@ -11,7 +11,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\GroupCenter;
|
||||
use Chill\MainBundle\Entity\PermissionsGroup;
|
||||
use Chill\MainBundle\Entity\RoleScope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
@@ -62,6 +65,15 @@ class LoadUsers extends AbstractFixture implements ContainerAwareInterface, Orde
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$roleScope = new RoleScope();
|
||||
$roleScope->setRole('CHILL_MAIN_COMPOSE_EXPORT');
|
||||
$permissionGroup = new PermissionsGroup();
|
||||
$permissionGroup->setName('export');
|
||||
$permissionGroup->addRoleScope($roleScope);
|
||||
|
||||
$manager->persist($roleScope);
|
||||
$manager->persist($permissionGroup);
|
||||
|
||||
foreach (self::$refs as $username => $params) {
|
||||
$user = new User();
|
||||
|
||||
@@ -81,7 +93,14 @@ class LoadUsers extends AbstractFixture implements ContainerAwareInterface, Orde
|
||||
->setEmail(sprintf('%s@chill.social', \str_replace(' ', '', (string) $username)));
|
||||
|
||||
foreach ($params['groupCenterRefs'] as $groupCenterRef) {
|
||||
$user->addGroupCenter($this->getReference($groupCenterRef, GroupCenter::class));
|
||||
$user->addGroupCenter($gc = $this->getReference($groupCenterRef, GroupCenter::class));
|
||||
|
||||
$exportGroupCenter = new GroupCenter();
|
||||
$exportGroupCenter->setPermissionsGroup($permissionGroup);
|
||||
$exportGroupCenter->setCenter($gc->getCenter());
|
||||
$manager->persist($exportGroupCenter);
|
||||
|
||||
$user->addGroupCenter($exportGroupCenter);
|
||||
}
|
||||
|
||||
echo 'Creating user '.$username."... \n";
|
||||
|
@@ -70,9 +70,9 @@ class NewsItem implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function setContent(string $content): void
|
||||
public function setContent(?string $content): void
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->content = (string) $content;
|
||||
}
|
||||
|
||||
public function getStartDate(): ?\DateTimeImmutable
|
||||
|
@@ -176,6 +176,14 @@ class SavedExport implements TrackCreationInterface, TrackUpdateInterface
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if shared with at least one user or one group.
|
||||
*/
|
||||
public function isShared(): bool
|
||||
{
|
||||
return $this->sharedWithUsers->count() > 0 || $this->sharedWithGroups->count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the user is shared with either directly or through a group.
|
||||
*
|
||||
|
@@ -147,12 +147,12 @@ final readonly class ExportFormHelper
|
||||
*/
|
||||
public function getPickedCenters(array $data): array
|
||||
{
|
||||
if (!array_key_exists('centers', $data) || !array_key_exists('regroupments', $data)) {
|
||||
if (!array_key_exists('centers', $data)) {
|
||||
throw new \RuntimeException('array has not the expected shape');
|
||||
}
|
||||
|
||||
$centers = $data['centers'] instanceof Collection ? $data['centers']->toArray() : $data['centers'];
|
||||
$regroupments = $data['regroupments'] instanceof Collection ? $data['regroupments']->toArray() : $data['regroupments'];
|
||||
$regroupments = ($data['regroupments'] ?? []) instanceof Collection ? $data['regroupments']->toArray() : ($data['regroupments'] ?? []);
|
||||
|
||||
return $this->centerRegroupementResolver->resolveCenters($regroupments, $centers);
|
||||
}
|
||||
|
@@ -144,11 +144,9 @@ class ExportManager
|
||||
/**
|
||||
* @param string $alias
|
||||
*
|
||||
* @return AggregatorInterface
|
||||
*
|
||||
* @throws \RuntimeException if the aggregator is not known
|
||||
*/
|
||||
public function getAggregator($alias)
|
||||
public function getAggregator($alias): AggregatorInterface
|
||||
{
|
||||
if (null === $aggregator = $this->aggregators[$alias] ?? null) {
|
||||
throw new \RuntimeException("The aggregator with alias {$alias} is not known.");
|
||||
|
@@ -1,241 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export\Formatter;
|
||||
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportManagerAwareInterface;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function count;
|
||||
|
||||
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
|
||||
|
||||
/**
|
||||
* Create a CSV List for the export.
|
||||
*/
|
||||
class CSVListFormatter implements FormatterInterface, ExportManagerAwareInterface
|
||||
{
|
||||
use ExportManagerAwareTrait;
|
||||
|
||||
protected $exportAlias;
|
||||
|
||||
protected $exportData;
|
||||
|
||||
|
||||
protected $formatterData;
|
||||
|
||||
/**
|
||||
* This variable cache the labels internally.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $labelsCache;
|
||||
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translatorInterface)
|
||||
{
|
||||
$this->translator = $translatorInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* build a form, which will be used to collect data required for the execution
|
||||
* of this formatter.
|
||||
*
|
||||
* @uses appendAggregatorForm
|
||||
*
|
||||
* @param type $exportAlias
|
||||
*/
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
$exportAlias,
|
||||
array $aggregatorAliases,
|
||||
): void {
|
||||
$builder->add('numerotation', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'yes' => true,
|
||||
'no' => false,
|
||||
],
|
||||
'expanded' => true,
|
||||
'multiple' => false,
|
||||
'label' => 'Add a number on first column',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return ['numerotation' => $formData['numerotation']];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return ['numerotation' => $formData['numerotation']];
|
||||
}
|
||||
|
||||
public function getFormDefaultData(array $aggregatorAliases): array
|
||||
{
|
||||
return ['numerotation' => true];
|
||||
}
|
||||
|
||||
public function getName(): string|TranslatableInterface
|
||||
{
|
||||
return 'CSV vertical list';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a response from the data collected on differents ExportElementInterface.
|
||||
*
|
||||
* @param mixed[] $result The result, as given by the ExportInterface
|
||||
* @param mixed[] $formatterData collected from the current form
|
||||
* @param string $exportAlias the id of the current export
|
||||
* @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
|
||||
* @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data
|
||||
*
|
||||
* @return Response The response to be shown
|
||||
*/
|
||||
public function getResponse(
|
||||
$result,
|
||||
$formatterData,
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData,
|
||||
ExportGenerationContext $context,
|
||||
) {
|
||||
$this->result = $result;
|
||||
$this->exportAlias = $exportAlias;
|
||||
$this->exportData = $exportData;
|
||||
$this->formatterData = $formatterData;
|
||||
|
||||
$output = fopen('php://output', 'wb');
|
||||
|
||||
$this->prepareHeaders($output);
|
||||
|
||||
$i = 1;
|
||||
|
||||
foreach ($result as $row) {
|
||||
$line = [];
|
||||
|
||||
if (true === $this->formatterData['numerotation']) {
|
||||
$line[] = $i;
|
||||
}
|
||||
|
||||
foreach ($row as $key => $value) {
|
||||
$line[] = $this->getLabel($key, $value);
|
||||
}
|
||||
|
||||
fputcsv($output, $line);
|
||||
|
||||
++$i;
|
||||
}
|
||||
|
||||
$csvContent = stream_get_contents($output);
|
||||
fclose($output);
|
||||
|
||||
$response = new Response();
|
||||
$response->setStatusCode(200);
|
||||
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
|
||||
// $response->headers->set('Content-Disposition','attachment; filename="export.csv"');
|
||||
|
||||
$response->setContent($csvContent);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return FormatterInterface::TYPE_LIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the label corresponding to the given key and value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
*
|
||||
* @throws \LogicException if the label is not found
|
||||
*/
|
||||
protected function getLabel($key, $value)
|
||||
{
|
||||
if (null === $this->labelsCache) {
|
||||
$this->prepareCacheLabels();
|
||||
}
|
||||
|
||||
if (!\array_key_exists($key, $this->labelsCache)) {
|
||||
throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($this->labelsCache))));
|
||||
}
|
||||
|
||||
return $this->labelsCache[$key]($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the label cache which will be used by getLabel. This function
|
||||
* should be called only once in the generation lifecycle.
|
||||
*/
|
||||
protected function prepareCacheLabels()
|
||||
{
|
||||
$export = $this->getExportManager()->getExport($this->exportAlias);
|
||||
$keys = $export->getQueryKeys($this->exportData);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
// get an array with all values for this key if possible
|
||||
$values = \array_map(static fn ($v) => $v[$key], $this->result);
|
||||
// store the label in the labelsCache property
|
||||
$this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add the headers to the csv file.
|
||||
*
|
||||
* @param resource $output
|
||||
*/
|
||||
protected function prepareHeaders($output)
|
||||
{
|
||||
$keys = $this->getExportManager()->getExport($this->exportAlias)->getQueryKeys($this->exportData);
|
||||
// we want to keep the order of the first row. So we will iterate on the first row of the results
|
||||
$first_row = \count($this->result) > 0 ? $this->result[0] : [];
|
||||
$header_line = [];
|
||||
|
||||
if (true === $this->formatterData['numerotation']) {
|
||||
$header_line[] = $this->translator->trans('Number');
|
||||
}
|
||||
|
||||
foreach ($first_row as $key => $value) {
|
||||
$content = $this->getLabel($key, '_header');
|
||||
if ($content instanceof TranslatableInterface) {
|
||||
$header_line[] = $content->trans($this->translator, $this->translator->getLocale());
|
||||
} else {
|
||||
$header_line[] = $this->translator->trans($this->getLabel($key, '_header'));
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($header_line) > 0) {
|
||||
fputcsv($output, $header_line);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,229 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export\Formatter;
|
||||
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportManagerAwareInterface;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* Create a CSV List for the export where the header are printed on the
|
||||
* first column, and the result goes from left to right.
|
||||
*/
|
||||
class CSVPivotedListFormatter implements FormatterInterface, ExportManagerAwareInterface
|
||||
{
|
||||
use ExportManagerAwareTrait;
|
||||
|
||||
protected $exportAlias;
|
||||
|
||||
protected $exportData;
|
||||
|
||||
protected $formatterData;
|
||||
|
||||
/**
|
||||
* This variable cache the labels internally.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $labelsCache;
|
||||
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translatorInterface)
|
||||
{
|
||||
$this->translator = $translatorInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* build a form, which will be used to collect data required for the execution
|
||||
* of this formatter.
|
||||
*
|
||||
* @uses appendAggregatorForm
|
||||
*
|
||||
* @param type $exportAlias
|
||||
*/
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
$exportAlias,
|
||||
array $aggregatorAliases,
|
||||
): void {
|
||||
$builder->add('numerotation', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'yes' => true,
|
||||
'no' => false,
|
||||
],
|
||||
'expanded' => true,
|
||||
'multiple' => false,
|
||||
'label' => 'Add a number on first column',
|
||||
'data' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return ['numerotation' => $formData['numerotation']];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return ['numerotation' => $formData['numerotation']];
|
||||
}
|
||||
|
||||
public function getFormDefaultData(array $aggregatorAliases): array
|
||||
{
|
||||
return ['numerotation' => true];
|
||||
}
|
||||
|
||||
public function getName(): string|\Symfony\Contracts\Translation\TranslatableInterface
|
||||
{
|
||||
return 'CSV horizontal list';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a response from the data collected on differents ExportElementInterface.
|
||||
*
|
||||
* @param mixed[] $result The result, as given by the ExportInterface
|
||||
* @param mixed[] $formatterData collected from the current form
|
||||
* @param string $exportAlias the id of the current export
|
||||
* @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
|
||||
* @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data
|
||||
*
|
||||
* @return Response The response to be shown
|
||||
*/
|
||||
public function getResponse(
|
||||
$result,
|
||||
$formatterData,
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData,
|
||||
ExportGenerationContext $context,
|
||||
) {
|
||||
$this->result = $result;
|
||||
$this->exportAlias = $exportAlias;
|
||||
$this->exportData = $exportData;
|
||||
$this->formatterData = $formatterData;
|
||||
|
||||
$output = fopen('php://output', 'wb');
|
||||
|
||||
$i = 1;
|
||||
$lines = [];
|
||||
$this->prepareHeaders($lines);
|
||||
|
||||
foreach ($result as $row) {
|
||||
$j = 0;
|
||||
|
||||
if (true === $this->formatterData['numerotation']) {
|
||||
$lines[$j][] = $i;
|
||||
++$j;
|
||||
}
|
||||
|
||||
foreach ($row as $key => $value) {
|
||||
$lines[$j][] = $this->getLabel($key, $value);
|
||||
++$j;
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
|
||||
// adding the lines to the csv output
|
||||
foreach ($lines as $line) {
|
||||
fputcsv($output, $line);
|
||||
}
|
||||
|
||||
$csvContent = stream_get_contents($output);
|
||||
fclose($output);
|
||||
|
||||
$response = new Response();
|
||||
$response->setStatusCode(200);
|
||||
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
|
||||
$response->headers->set('Content-Disposition', 'attachment; filename="export.csv"');
|
||||
|
||||
$response->setContent($csvContent);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return FormatterInterface::TYPE_LIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the label corresponding to the given key and value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
*
|
||||
* @throws \LogicException if the label is not found
|
||||
*/
|
||||
protected function getLabel($key, $value)
|
||||
{
|
||||
if (null === $this->labelsCache) {
|
||||
$this->prepareCacheLabels();
|
||||
}
|
||||
|
||||
return $this->labelsCache[$key]($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the label cache which will be used by getLabel. This function
|
||||
* should be called only once in the generation lifecycle.
|
||||
*/
|
||||
protected function prepareCacheLabels()
|
||||
{
|
||||
$export = $this->getExportManager()->getExport($this->exportAlias);
|
||||
$keys = $export->getQueryKeys($this->exportData);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
// get an array with all values for this key if possible
|
||||
$values = \array_map(static fn ($v) => $v[$key], $this->result);
|
||||
// store the label in the labelsCache property
|
||||
$this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add the headers to lines array.
|
||||
*
|
||||
* @param array $lines the lines where the header will be added
|
||||
*/
|
||||
protected function prepareHeaders(array &$lines)
|
||||
{
|
||||
$keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData);
|
||||
// we want to keep the order of the first row. So we will iterate on the first row of the results
|
||||
$first_row = \count($this->result) > 0 ? $this->result[0] : [];
|
||||
$header_line = [];
|
||||
|
||||
if (true === $this->formatterData['numerotation']) {
|
||||
$lines[] = [$this->translator->trans('Number')];
|
||||
}
|
||||
|
||||
foreach ($first_row as $key => $value) {
|
||||
$lines[] = [$this->getLabel($key, '_header')];
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,110 +12,25 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Export\Formatter;
|
||||
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManagerAwareInterface;
|
||||
use Chill\MainBundle\Export\FormattedExportGeneration;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInterface
|
||||
final class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInterface
|
||||
{
|
||||
use ExportManagerAwareTrait;
|
||||
|
||||
/**
|
||||
* an array where keys are the aggregators aliases and
|
||||
* values are the data.
|
||||
*
|
||||
* replaced when `getResponse` is called.
|
||||
*/
|
||||
protected array $aggregatorsData;
|
||||
|
||||
/**
|
||||
* The export.
|
||||
*
|
||||
* replaced when `getResponse` is called.
|
||||
*
|
||||
* @var \Chill\MainBundle\Export\ExportInterface
|
||||
*/
|
||||
protected $export;
|
||||
|
||||
|
||||
/**
|
||||
* array containing value of export form.
|
||||
*
|
||||
* replaced when `getResponse` is called.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $exportData;
|
||||
|
||||
/**
|
||||
* replaced when `getResponse` is called.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $filtersData;
|
||||
|
||||
/**
|
||||
* replaced when `getResponse` is called.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $formatterData;
|
||||
|
||||
/**
|
||||
* The result, as returned by the export.
|
||||
*
|
||||
* replaced when `getResponse` is called.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* replaced when `getResponse` is called.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
// protected $labels;
|
||||
|
||||
/**
|
||||
* temporary file to store spreadsheet.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tempfile;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* cache for displayable result.
|
||||
*
|
||||
* This cache is reset when `getResponse` is called.
|
||||
*
|
||||
* The array's keys are the keys in the raw result, and
|
||||
* values are the callable which will transform the raw result to
|
||||
* displayable result.
|
||||
*/
|
||||
private ?array $cacheDisplayableResult = null;
|
||||
|
||||
/**
|
||||
* Whethe `cacheDisplayableResult` is initialized or not.
|
||||
*/
|
||||
private bool $cacheDisplayableResultIsInitialized = false;
|
||||
|
||||
public function __construct(TranslatorInterface $translatorInterface)
|
||||
{
|
||||
$this->translator = $translatorInterface;
|
||||
}
|
||||
public function __construct(private readonly TranslatorInterface $translator) {}
|
||||
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
@@ -178,6 +93,51 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
return 'SpreadSheet (xlsx, ods)';
|
||||
}
|
||||
|
||||
public function generate(
|
||||
$result,
|
||||
$formatterData,
|
||||
string $exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData,
|
||||
ExportGenerationContext $context,
|
||||
) {
|
||||
// Initialize local variables instead of class properties
|
||||
/** @var ExportInterface $export */
|
||||
$export = $this->getExportManager()->getExport($exportAlias);
|
||||
|
||||
// Initialize cache variables
|
||||
$cacheDisplayableResult = $this->initializeDisplayable($result, $export, $exportData, $aggregatorsData);
|
||||
|
||||
$tempfile = \tempnam(\sys_get_temp_dir(), '');
|
||||
|
||||
if (false === $tempfile) {
|
||||
throw new \RuntimeException('Unable to create temporary file');
|
||||
}
|
||||
|
||||
$this->generateContent(
|
||||
$context,
|
||||
$tempfile,
|
||||
$result,
|
||||
$formatterData,
|
||||
$export,
|
||||
$exportData,
|
||||
$filtersData,
|
||||
$aggregatorsData,
|
||||
$cacheDisplayableResult,
|
||||
);
|
||||
|
||||
$result = new FormattedExportGeneration(
|
||||
file_get_contents($tempfile),
|
||||
$this->getContentType($formatterData['format']),
|
||||
);
|
||||
|
||||
// remove the temp file from disk
|
||||
\unlink($tempfile);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getResponse(
|
||||
$result,
|
||||
$formatterData,
|
||||
@@ -187,33 +147,10 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
array $aggregatorsData,
|
||||
ExportGenerationContext $context,
|
||||
): Response {
|
||||
// store all data when the process is initiated
|
||||
$this->result = $result;
|
||||
$this->formatterData = $formatterData;
|
||||
$this->export = $this->getExportManager()->getExport($exportAlias);
|
||||
$this->exportData = $exportData;
|
||||
$this->filtersData = $filtersData;
|
||||
$this->aggregatorsData = $aggregatorsData;
|
||||
$formattedResult = $this->generate($result, $formatterData, $exportAlias, $exportData, $filtersData, $aggregatorsData, $context);
|
||||
|
||||
// reset cache
|
||||
$this->cacheDisplayableResult = [];
|
||||
$this->cacheDisplayableResultIsInitialized = false;
|
||||
|
||||
$response = new Response();
|
||||
$response->headers->set(
|
||||
'Content-Type',
|
||||
$this->getContentType($this->formatterData['format'])
|
||||
);
|
||||
|
||||
$this->tempfile = \tempnam(\sys_get_temp_dir(), '');
|
||||
$this->generateContent($context);
|
||||
|
||||
$f = \fopen($this->tempfile, 'rb');
|
||||
$response->setContent(\stream_get_contents($f));
|
||||
fclose($f);
|
||||
|
||||
// remove the temp file from disk
|
||||
\unlink($this->tempfile);
|
||||
$response = new BinaryFileResponse($formattedResult->content);
|
||||
$response->headers->set('Content-Type', $formattedResult->contentType);
|
||||
|
||||
return $response;
|
||||
}
|
||||
@@ -223,7 +160,7 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
return 'tabular';
|
||||
}
|
||||
|
||||
protected function addContentTable(
|
||||
private function addContentTable(
|
||||
Worksheet $worksheet,
|
||||
$sortedResults,
|
||||
$line,
|
||||
@@ -245,11 +182,11 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
*
|
||||
* @return int the line number after the last description
|
||||
*/
|
||||
protected function addFiltersDescription(Worksheet &$worksheet, ExportGenerationContext $context)
|
||||
private function addFiltersDescription(Worksheet &$worksheet, ExportGenerationContext $context, array $filtersData)
|
||||
{
|
||||
$line = 3;
|
||||
|
||||
foreach ($this->filtersData as $alias => $data) {
|
||||
foreach ($filtersData as $alias => $data) {
|
||||
$filter = $this->getExportManager()->getFilter($alias);
|
||||
$description = $filter->describeAction($data, $context);
|
||||
if (\is_array($description)) {
|
||||
@@ -274,26 +211,22 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
*
|
||||
* return the line number where the next content (i.e. result) should
|
||||
* be appended.
|
||||
*
|
||||
* @param int $line
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function addHeaders(
|
||||
private function addHeaders(
|
||||
Worksheet &$worksheet,
|
||||
array $globalKeys,
|
||||
$line,
|
||||
) {
|
||||
int $line,
|
||||
array $cacheDisplayableResult = [],
|
||||
): int {
|
||||
// get the displayable form of headers
|
||||
$displayables = [];
|
||||
|
||||
foreach ($globalKeys as $key) {
|
||||
$displayable = $this->getDisplayableResult($key, '_header');
|
||||
$displayable = $this->getDisplayableResult($key, '_header', $cacheDisplayableResult);
|
||||
|
||||
if ($displayable instanceof TranslatableInterface) {
|
||||
$displayables[] = $displayable->trans($this->translator, $this->translator->getLocale());
|
||||
} else {
|
||||
$displayables[] = $this->translator->trans($this->getDisplayableResult($key, '_header'));
|
||||
$displayables[] = $this->translator->trans($this->getDisplayableResult($key, '_header', $cacheDisplayableResult));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,9 +244,9 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
* Add the title to the worksheet and merge the cell containing
|
||||
* the title.
|
||||
*/
|
||||
protected function addTitleToWorkSheet(Worksheet &$worksheet)
|
||||
private function addTitleToWorkSheet(Worksheet &$worksheet, $export)
|
||||
{
|
||||
$worksheet->setCellValue('A1', $this->getTitle());
|
||||
$worksheet->setCellValue('A1', $this->getTitle($export));
|
||||
$worksheet->mergeCells('A1:G1');
|
||||
}
|
||||
|
||||
@@ -322,14 +255,14 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
*
|
||||
* @return array where 1st member is spreadsheet, 2nd is worksheet
|
||||
*/
|
||||
protected function createSpreadsheet()
|
||||
private function createSpreadsheet($export)
|
||||
{
|
||||
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
// setting the worksheet title and code name
|
||||
$worksheet
|
||||
->setTitle($this->getTitle())
|
||||
->setTitle($this->getTitle($export))
|
||||
->setCodeName('result');
|
||||
|
||||
return [$spreadsheet, $worksheet];
|
||||
@@ -338,29 +271,38 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
/**
|
||||
* Generate the content and write it to php://temp.
|
||||
*/
|
||||
protected function generateContent(ExportGenerationContext $context)
|
||||
{
|
||||
[$spreadsheet, $worksheet] = $this->createSpreadsheet();
|
||||
private function generateContent(
|
||||
ExportGenerationContext $context,
|
||||
string $tempfile,
|
||||
$result,
|
||||
$formatterData,
|
||||
$export,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData,
|
||||
array $cacheDisplayableResult,
|
||||
) {
|
||||
[$spreadsheet, $worksheet] = $this->createSpreadsheet($export);
|
||||
|
||||
$this->addTitleToWorkSheet($worksheet);
|
||||
$line = $this->addFiltersDescription($worksheet, $context);
|
||||
$this->addTitleToWorkSheet($worksheet, $export);
|
||||
$line = $this->addFiltersDescription($worksheet, $context, $filtersData);
|
||||
|
||||
// at this point, we are going to sort retsults for an easier manipulation
|
||||
// at this point, we are going to sort results for an easier manipulation
|
||||
[$sortedResult, $exportKeys, $aggregatorKeys, $globalKeys] =
|
||||
$this->sortResult();
|
||||
$this->sortResult($result, $export, $exportData, $aggregatorsData, $formatterData, $cacheDisplayableResult);
|
||||
|
||||
$line = $this->addHeaders($worksheet, $globalKeys, $line);
|
||||
$line = $this->addHeaders($worksheet, $globalKeys, $line, $cacheDisplayableResult);
|
||||
|
||||
$line = $this->addContentTable($worksheet, $sortedResult, $line);
|
||||
$this->addContentTable($worksheet, $sortedResult, $line);
|
||||
|
||||
$writer = match ($this->formatterData['format']) {
|
||||
$writer = match ($formatterData['format']) {
|
||||
'ods' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods'),
|
||||
'xlsx' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'),
|
||||
'csv' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Csv'),
|
||||
default => throw new \LogicException(),
|
||||
};
|
||||
|
||||
$writer->save($this->tempfile);
|
||||
$writer->save($tempfile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,7 +311,7 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
*
|
||||
* @return string[] an array containing the keys of aggregators
|
||||
*/
|
||||
protected function getAggregatorKeysSorted()
|
||||
private function getAggregatorKeysSorted(array $aggregatorsData, array $formatterData)
|
||||
{
|
||||
// empty array for aggregators keys
|
||||
$keys = [];
|
||||
@@ -377,7 +319,7 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
// during sorting
|
||||
$aggregatorKeyAssociation = [];
|
||||
|
||||
foreach ($this->aggregatorsData as $alias => $data) {
|
||||
foreach ($aggregatorsData as $alias => $data) {
|
||||
$aggregator = $this->exportManager->getAggregator($alias);
|
||||
$aggregatorsKeys = $aggregator->getQueryKeys($data);
|
||||
// append the keys from aggregator to the $keys existing array
|
||||
@@ -389,9 +331,9 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
}
|
||||
|
||||
// sort the result using the form
|
||||
usort($keys, function ($a, $b) use ($aggregatorKeyAssociation) {
|
||||
$A = $this->formatterData[$aggregatorKeyAssociation[$a]]['order'];
|
||||
$B = $this->formatterData[$aggregatorKeyAssociation[$b]]['order'];
|
||||
usort($keys, function ($a, $b) use ($aggregatorKeyAssociation, $formatterData) {
|
||||
$A = $formatterData[$aggregatorKeyAssociation[$a]]['order'];
|
||||
$B = $formatterData[$aggregatorKeyAssociation[$b]]['order'];
|
||||
|
||||
if ($A === $B) {
|
||||
return 0;
|
||||
@@ -407,7 +349,7 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
return $keys;
|
||||
}
|
||||
|
||||
protected function getContentType($format)
|
||||
private function getContentType($format)
|
||||
{
|
||||
switch ($format) {
|
||||
case 'csv':
|
||||
@@ -424,23 +366,20 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
|
||||
/**
|
||||
* Get the displayable result.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
protected function getDisplayableResult($key, mixed $value)
|
||||
{
|
||||
if (false === $this->cacheDisplayableResultIsInitialized) {
|
||||
$this->initializeCache($key);
|
||||
}
|
||||
|
||||
private function getDisplayableResult(
|
||||
string $key,
|
||||
mixed $value,
|
||||
array $cacheDisplayableResult,
|
||||
): string|TranslatableInterface|\DateTimeInterface|int|float|bool {
|
||||
$value ??= '';
|
||||
|
||||
return \call_user_func($this->cacheDisplayableResult[$key], $value);
|
||||
return \call_user_func($cacheDisplayableResult[$key], $value);
|
||||
}
|
||||
|
||||
protected function getTitle(): string
|
||||
private function getTitle($export): string
|
||||
{
|
||||
$original = $this->export->getTitle();
|
||||
$original = $export->getTitle();
|
||||
|
||||
if ($original instanceof TranslatableInterface) {
|
||||
$title = $original->trans($this->translator, $this->translator->getLocale());
|
||||
@@ -455,8 +394,13 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
return $title;
|
||||
}
|
||||
|
||||
protected function initializeCache($key)
|
||||
{
|
||||
private function initializeDisplayable(
|
||||
$result,
|
||||
ExportInterface $export,
|
||||
array $exportData,
|
||||
array $aggregatorsData,
|
||||
): array {
|
||||
$cacheDisplayableResult = [];
|
||||
/*
|
||||
* this function follows the following steps :
|
||||
*
|
||||
@@ -469,12 +413,11 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
// 1. create an associative array with key and export / aggregator
|
||||
$keysExportElementAssociation = [];
|
||||
// keys for export
|
||||
foreach ($this->export->getQueryKeys($this->exportData) as $key) {
|
||||
$keysExportElementAssociation[$key] = [$this->export,
|
||||
$this->exportData, ];
|
||||
foreach ($export->getQueryKeys($exportData) as $key) {
|
||||
$keysExportElementAssociation[$key] = [$export, $exportData];
|
||||
}
|
||||
// keys for aggregator
|
||||
foreach ($this->aggregatorsData as $alias => $data) {
|
||||
foreach ($aggregatorsData as $alias => $data) {
|
||||
$aggregator = $this->getExportManager()->getAggregator($alias);
|
||||
|
||||
foreach ($aggregator->getQueryKeys($data) as $key) {
|
||||
@@ -487,7 +430,7 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
|
||||
$allValues = [];
|
||||
// store all the values in an array
|
||||
foreach ($this->result as $row) {
|
||||
foreach ($result as $row) {
|
||||
foreach ($keys as $key) {
|
||||
$allValues[$key][] = $row[$key];
|
||||
}
|
||||
@@ -498,15 +441,14 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
foreach ($keysExportElementAssociation as $key => [$element, $data]) {
|
||||
// handle the case when there is not results lines (query is empty)
|
||||
if ([] === $allValues) {
|
||||
$this->cacheDisplayableResult[$key] = $element->getLabels($key, ['_header'], $data);
|
||||
$cacheDisplayableResult[$key] = $element->getLabels($key, ['_header'], $data);
|
||||
} else {
|
||||
$this->cacheDisplayableResult[$key] =
|
||||
$cacheDisplayableResult[$key] =
|
||||
$element->getLabels($key, \array_unique($allValues[$key]), $data);
|
||||
}
|
||||
}
|
||||
|
||||
// the cache is initialized !
|
||||
$this->cacheDisplayableResultIsInitialized = true;
|
||||
return $cacheDisplayableResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -544,23 +486,28 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
protected function sortResult()
|
||||
{
|
||||
private function sortResult(
|
||||
$result,
|
||||
ExportInterface $export,
|
||||
array $exportData,
|
||||
array $aggregatorsData,
|
||||
array $formatterData,
|
||||
array $cacheDisplayableResult,
|
||||
) {
|
||||
// get the keys for each row
|
||||
$exportKeys = $this->export->getQueryKeys($this->exportData);
|
||||
$aggregatorKeys = $this->getAggregatorKeysSorted();
|
||||
|
||||
$exportKeys = $export->getQueryKeys($exportData);
|
||||
$aggregatorKeys = $this->getAggregatorKeysSorted($aggregatorsData, $formatterData);
|
||||
$globalKeys = \array_merge($aggregatorKeys, $exportKeys);
|
||||
|
||||
$sortedResult = \array_map(function ($row) use ($globalKeys) {
|
||||
$sortedResult = \array_map(function ($row) use ($globalKeys, $cacheDisplayableResult) {
|
||||
$newRow = [];
|
||||
|
||||
foreach ($globalKeys as $key) {
|
||||
$newRow[] = $this->getDisplayableResult($key, $row[$key]);
|
||||
$newRow[] = $this->getDisplayableResult($key, $row[$key], $cacheDisplayableResult);
|
||||
}
|
||||
|
||||
return $newRow;
|
||||
}, $this->result);
|
||||
}, $result);
|
||||
|
||||
\array_multisort($sortedResult);
|
||||
|
||||
|
@@ -13,6 +13,7 @@ namespace Chill\MainBundle\Export\Formatter;
|
||||
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportManagerAwareInterface;
|
||||
use Chill\MainBundle\Export\FormattedExportGeneration;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
@@ -21,7 +22,9 @@ use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
@@ -31,42 +34,17 @@ class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAware
|
||||
{
|
||||
use ExportManagerAwareTrait;
|
||||
|
||||
protected $exportAlias;
|
||||
|
||||
protected $exportData;
|
||||
|
||||
protected $formatterData;
|
||||
|
||||
/**
|
||||
* This variable cache the labels internally.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $labelsCache;
|
||||
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translatorInterface)
|
||||
{
|
||||
$this->translator = $translatorInterface;
|
||||
}
|
||||
public function __construct(private readonly TranslatorInterface $translator) {}
|
||||
|
||||
/**
|
||||
* build a form, which will be used to collect data required for the execution
|
||||
* of this formatter.
|
||||
*
|
||||
* @uses appendAggregatorForm
|
||||
*
|
||||
* @param string $exportAlias
|
||||
*/
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
$exportAlias,
|
||||
string $exportAlias,
|
||||
array $aggregatorAliases,
|
||||
): void {
|
||||
$builder
|
||||
@@ -108,54 +86,32 @@ class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAware
|
||||
return ['numerotation' => true, 'format' => 'xlsx'];
|
||||
}
|
||||
|
||||
public function getName(): string|\Symfony\Contracts\Translation\TranslatableInterface
|
||||
public function getName(): string|TranslatableInterface
|
||||
{
|
||||
return 'Spreadsheet list formatter (.xlsx, .ods)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a response from the data collected on differents ExportElementInterface.
|
||||
*
|
||||
* @param mixed[] $result The result, as given by the ExportInterface
|
||||
* @param mixed[] $formatterData collected from the current form
|
||||
* @param string $exportAlias the id of the current export
|
||||
* @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
|
||||
* @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data
|
||||
*
|
||||
* @return Response The response to be shown
|
||||
*/
|
||||
public function getResponse(
|
||||
$result,
|
||||
$formatterData,
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData,
|
||||
ExportGenerationContext $context,
|
||||
) {
|
||||
$this->result = $result;
|
||||
$this->exportAlias = $exportAlias;
|
||||
$this->exportData = $exportData;
|
||||
$this->formatterData = $formatterData;
|
||||
|
||||
public function generate($result, $formatterData, string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData, ExportGenerationContext $context): FormattedExportGeneration
|
||||
{
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
$cacheLabels = $this->prepareCacheLabels($result, $exportAlias, $exportData);
|
||||
|
||||
$this->prepareHeaders($worksheet);
|
||||
$this->prepareHeaders($cacheLabels, $worksheet, $result, $formatterData, $exportAlias, $exportData);
|
||||
|
||||
$i = 1;
|
||||
|
||||
foreach ($result as $row) {
|
||||
if (true === $this->formatterData['numerotation']) {
|
||||
if (true === $formatterData['numerotation']) {
|
||||
$worksheet->setCellValue('A'.($i + 1), (string) $i);
|
||||
}
|
||||
|
||||
$a = $this->formatterData['numerotation'] ? 'B' : 'A';
|
||||
$a = $formatterData['numerotation'] ? 'B' : 'A';
|
||||
|
||||
foreach ($row as $key => $value) {
|
||||
$row = $a.($i + 1);
|
||||
|
||||
$formattedValue = $this->getLabel($key, $value);
|
||||
$formattedValue = $this->getLabel($cacheLabels, $key, $value, $result, $exportAlias, $exportData);
|
||||
|
||||
if ($formattedValue instanceof \DateTimeInterface) {
|
||||
$worksheet->setCellValue($row, Date::PHPToExcel($formattedValue));
|
||||
@@ -169,6 +125,8 @@ class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAware
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
|
||||
}
|
||||
} elseif ($formattedValue instanceof TranslatableInterface) {
|
||||
$worksheet->setCellValue($row, $formattedValue->trans($this->translator));
|
||||
} else {
|
||||
$worksheet->setCellValue($row, $formattedValue);
|
||||
}
|
||||
@@ -178,7 +136,7 @@ class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAware
|
||||
++$i;
|
||||
}
|
||||
|
||||
switch ($this->formatterData['format']) {
|
||||
switch ($formatterData['format']) {
|
||||
case 'ods':
|
||||
$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods');
|
||||
$contentType = 'application/vnd.oasis.opendocument.spreadsheet';
|
||||
@@ -201,22 +159,48 @@ class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAware
|
||||
default:
|
||||
// this should not happen
|
||||
// throw an exception to ensure that the error is catched
|
||||
throw new \OutOfBoundsException('The format '.$this->formatterData['format'].' is not supported');
|
||||
throw new \OutOfBoundsException('The format '.$formatterData['format'].' is not supported');
|
||||
}
|
||||
|
||||
$response = new Response();
|
||||
$response->headers->set('content-type', $contentType);
|
||||
|
||||
$tempfile = \tempnam(\sys_get_temp_dir(), '');
|
||||
$writer->save($tempfile);
|
||||
|
||||
$f = \fopen($tempfile, 'rb');
|
||||
$response->setContent(\stream_get_contents($f));
|
||||
fclose($f);
|
||||
$generated = new FormattedExportGeneration(
|
||||
file_get_contents($tempfile),
|
||||
$contentType,
|
||||
);
|
||||
|
||||
// remove the temp file from disk
|
||||
\unlink($tempfile);
|
||||
|
||||
return $generated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a response from the data collected on differents ExportElementInterface.
|
||||
*
|
||||
* @param mixed[] $result The result, as given by the ExportInterface
|
||||
* @param mixed[] $formatterData collected from the current form
|
||||
* @param string $exportAlias the id of the current export
|
||||
* @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
|
||||
* @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data
|
||||
*
|
||||
* @return Response The response to be shown
|
||||
*/
|
||||
public function getResponse(
|
||||
$result,
|
||||
$formatterData,
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData,
|
||||
ExportGenerationContext $context,
|
||||
) {
|
||||
$generated = $this->generate($result, $formatterData, $exportAlias, $exportData, $filtersData, $aggregatorsData, $context);
|
||||
|
||||
$response = new BinaryFileResponse($generated->content);
|
||||
$response->headers->set('Content-Type', $generated->contentType);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -228,34 +212,29 @@ class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAware
|
||||
/**
|
||||
* Give the label corresponding to the given key and value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
* @return string|\DateTimeInterface|int|float|TranslatableInterface|null
|
||||
*
|
||||
* @throws \LogicException if the label is not found
|
||||
*/
|
||||
protected function getLabel($key, $value)
|
||||
private function getLabel(array $labelsCache, $key, $value, array $result, string $exportAlias, array $exportData)
|
||||
{
|
||||
if (null === $this->labelsCache) {
|
||||
$this->prepareCacheLabels();
|
||||
if (!\array_key_exists($key, $labelsCache)) {
|
||||
throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($labelsCache))));
|
||||
}
|
||||
|
||||
if (!\array_key_exists($key, $this->labelsCache)) {
|
||||
throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($this->labelsCache))));
|
||||
}
|
||||
|
||||
return $this->labelsCache[$key]($value);
|
||||
return $labelsCache[$key]($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the label cache which will be used by getLabel. This function
|
||||
* should be called only once in the generation lifecycle.
|
||||
* Prepare the label cache which will be used by getLabel.
|
||||
*
|
||||
* @return array The labels cache
|
||||
*/
|
||||
protected function prepareCacheLabels()
|
||||
private function prepareCacheLabels(array $result, string $exportAlias, array $exportData): array
|
||||
{
|
||||
$export = $this->getExportManager()->getExport($this->exportAlias);
|
||||
$keys = $export->getQueryKeys($this->exportData);
|
||||
$labelsCache = [];
|
||||
$export = $this->getExportManager()->getExport($exportAlias);
|
||||
$keys = $export->getQueryKeys($exportData);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
// get an array with all values for this key if possible
|
||||
@@ -265,29 +244,31 @@ class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAware
|
||||
}
|
||||
|
||||
return $v[$key];
|
||||
}, $this->result);
|
||||
// store the label in the labelsCache property
|
||||
$this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData);
|
||||
}, $result);
|
||||
// store the label in the labelsCache
|
||||
$labelsCache[$key] = $export->getLabels($key, $values, $exportData);
|
||||
}
|
||||
|
||||
return $labelsCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* add the headers to the csv file.
|
||||
*/
|
||||
protected function prepareHeaders(Worksheet $worksheet)
|
||||
protected function prepareHeaders(array $labelsCache, Worksheet $worksheet, array $result, array $formatterData, string $exportAlias, array $exportData)
|
||||
{
|
||||
$keys = $this->getExportManager()->getExport($this->exportAlias)->getQueryKeys($this->exportData);
|
||||
$keys = $this->getExportManager()->getExport($exportAlias)->getQueryKeys($exportData);
|
||||
// we want to keep the order of the first row. So we will iterate on the first row of the results
|
||||
$first_row = \count($this->result) > 0 ? $this->result[0] : [];
|
||||
$first_row = \count($result) > 0 ? $result[0] : [];
|
||||
$header_line = [];
|
||||
|
||||
if (true === $this->formatterData['numerotation']) {
|
||||
if (true === $formatterData['numerotation']) {
|
||||
$header_line[] = $this->translator->trans('Number');
|
||||
}
|
||||
|
||||
foreach ($first_row as $key => $value) {
|
||||
$header_line[] = $this->translator->trans(
|
||||
$this->getLabel($key, '_header')
|
||||
$this->getLabel($labelsCache, $key, '_header', $result, $exportAlias, $exportData)
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@ use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
#[AsMessageHandler]
|
||||
class RemoveExportGenerationMessageHandler implements MessageHandlerInterface
|
||||
final readonly class RemoveExportGenerationMessageHandler implements MessageHandlerInterface
|
||||
{
|
||||
private const LOG_PREFIX = '[RemoveExportGenerationMessageHandler] ';
|
||||
|
||||
|
@@ -202,10 +202,6 @@ export interface WorkflowAttachment {
|
||||
genericDoc: null | GenericDoc;
|
||||
}
|
||||
|
||||
export interface PrivateCommentEmbeddable {
|
||||
comments: Record<number, string>;
|
||||
}
|
||||
|
||||
export interface ExportGeneration {
|
||||
id: string;
|
||||
type: "export_generation";
|
||||
@@ -215,3 +211,7 @@ export interface ExportGeneration {
|
||||
status: StoredObjectStatus;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface PrivateCommentEmbeddable {
|
||||
comments: Record<number, string>;
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<span class="chill-entity entity-user">
|
||||
{{ user.label }}
|
||||
<span class="user-job" v-if="user.user_job !== null"
|
||||
> ({{ localizeString(user.user_job.label) }})</span
|
||||
<span class="user-job" v-if="user.user_job !== null">
|
||||
({{ localizeString(user.user_job.label) }})</span
|
||||
>
|
||||
<span class="main-scope" v-if="user.main_scope !== null"
|
||||
> ({{ localizeString(user.main_scope.name) }})</span
|
||||
<span class="main-scope" v-if="user.main_scope !== null">
|
||||
({{ localizeString(user.main_scope.name) }})</span
|
||||
>
|
||||
<span
|
||||
v-if="user.isAbsent"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
{% endblock crud_content_header %}
|
||||
|
||||
{% block crud_content_view %}
|
||||
|
||||
|
||||
{% block crud_content_view_details %}
|
||||
<dl class="chill_view_data">
|
||||
<dt>id</dt>
|
||||
@@ -20,7 +20,7 @@
|
||||
{{ 'Cancel'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% block content_view_actions_before %}{% endblock %}
|
||||
{% block content_form_actions_delete %}
|
||||
{% if chill_crud_action_exists(crud_name, 'delete') %}
|
||||
@@ -32,7 +32,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock content_form_actions_delete %}
|
||||
{% endblock content_form_actions_delete %}
|
||||
{% block content_view_actions_duplicate_link %}
|
||||
{% if chill_crud_action_exists(crud_name, 'new') %}
|
||||
{% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %}
|
||||
@@ -44,17 +44,6 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock content_view_actions_duplicate_link %}
|
||||
{% block content_view_actions_merge %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_thirdparty_find_duplicate',
|
||||
{ 'thirdparty_id': entity.id }) }}"
|
||||
title="{{ 'Merge'|trans }}"
|
||||
class="btn btn-misc">
|
||||
<i class="bi bi-chevron-contract"></i>
|
||||
{{ 'Merge'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% block content_view_actions_edit_link %}
|
||||
{% if chill_crud_action_exists(crud_name, 'edit') %}
|
||||
{% if is_granted(chill_crud_config('role', crud_name, 'edit'), entity) %}
|
||||
|
@@ -30,6 +30,7 @@
|
||||
{% if app.user is same as saved.user %}
|
||||
<p class="card-text tags">
|
||||
{% if app.user is same as saved.user %}<span class="badge bg-primary">{{ 'saved_export.Owner'|trans }}</span>{% endif %}
|
||||
{% if saved.isShared() %}<span class="badge bg-info">{{ 'saved_export.Shared with others'|trans }}</span>{% endif %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="card-text tags">
|
||||
|
@@ -38,7 +38,10 @@ final class SavedExportVoter extends Voter
|
||||
self::DUPLICATE,
|
||||
];
|
||||
|
||||
public function __construct(private readonly ExportManager $exportManager, private readonly AccessDecisionManagerInterface $accessDecisionManager) {}
|
||||
public function __construct(
|
||||
private readonly ExportManager $exportManager,
|
||||
private readonly AccessDecisionManagerInterface $accessDecisionManager,
|
||||
) {}
|
||||
|
||||
protected function supports($attribute, $subject): bool
|
||||
{
|
||||
@@ -55,7 +58,8 @@ final class SavedExportVoter extends Voter
|
||||
}
|
||||
|
||||
return match ($attribute) {
|
||||
self::DELETE, self::EDIT, self::SHARE => $subject->getUser() === $token->getUser(),
|
||||
self::DELETE, self::EDIT => $subject->getUser() === $token->getUser(),
|
||||
self::SHARE => $subject->getUser() === $token->getUser() && $this->accessDecisionManager->decide($token, [ChillExportVoter::COMPOSE_EXPORT]),
|
||||
self::DUPLICATE => $this->accessDecisionManager->decide($token, [ChillExportVoter::COMPOSE_EXPORT]) && $this->accessDecisionManager->decide($token, [self::EDIT], $subject) ,
|
||||
self::GENERATE => $this->canUserGenerate($user, $subject),
|
||||
default => throw new \UnexpectedValueException('attribute not supported: '.$attribute),
|
||||
|
@@ -58,7 +58,6 @@ final readonly class CancelStaleWorkflowHandler
|
||||
$transitions = $workflowComponent->getEnabledTransitions($workflow);
|
||||
|
||||
$transitionApplied = false;
|
||||
$wasInInitialPosition = 'initial' === $workflow->getStep();
|
||||
|
||||
foreach ($transitions as $transition) {
|
||||
if ($this->willTransitionLeadToFinalNegative($transition, $metadataStore)) {
|
||||
@@ -80,10 +79,6 @@ final readonly class CancelStaleWorkflowHandler
|
||||
throw new UnrecoverableMessageHandlingException(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId));
|
||||
}
|
||||
|
||||
if ($wasInInitialPosition) {
|
||||
$this->em->remove($workflow);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
|
@@ -34,119 +34,6 @@ abstract class AbstractAggregatorTest extends KernelTestCase
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* provide data for `testAliasDidNotDisappears`.
|
||||
*/
|
||||
public static function dataProviderAliasDidNotDisappears()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of data to normalize.
|
||||
*
|
||||
* @return iterable{array}
|
||||
*/
|
||||
public static function dataProviderFormDataToNormalize(): iterable
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data, 1, []];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* provide data for `testAlterQuery`.
|
||||
*/
|
||||
public static function dataProviderAlterQuery()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function dataProviderQueryExecution(): iterable
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare data for `testGetQueryKeys`.
|
||||
*/
|
||||
public static function dataProviderGetQueryKeys()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach ($datas as $data) {
|
||||
yield [$data];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare date for method `testGetResultsAndLabels`.
|
||||
*/
|
||||
public static function dataProviderGetResultsAndLabels()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach ($datas as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an aggregator instance which will be used in tests.
|
||||
*
|
||||
@@ -214,6 +101,28 @@ abstract class AbstractAggregatorTest extends KernelTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* provide data for `testAliasDidNotDisappears`.
|
||||
*/
|
||||
public static function dataProviderAliasDidNotDisappears()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderQueryExecution
|
||||
*
|
||||
@@ -228,6 +137,25 @@ abstract class AbstractAggregatorTest extends KernelTestCase
|
||||
self::assertIsArray($actual);
|
||||
}
|
||||
|
||||
public static function dataProviderQueryExecution(): iterable
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test the alteration of query by the filter.
|
||||
*
|
||||
@@ -266,6 +194,28 @@ abstract class AbstractAggregatorTest extends KernelTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* provide data for `testAlterQuery`.
|
||||
*/
|
||||
public static function dataProviderAlterQuery()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the `applyOn` method.
|
||||
*/
|
||||
@@ -309,6 +259,18 @@ abstract class AbstractAggregatorTest extends KernelTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of data to normalize.
|
||||
*
|
||||
* @return iterable{array}
|
||||
*/
|
||||
public static function dataProviderFormDataToNormalize(): iterable
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data, 1, []];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the query keys are strings.
|
||||
*
|
||||
@@ -335,6 +297,22 @@ abstract class AbstractAggregatorTest extends KernelTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare data for `testGetQueryKeys`.
|
||||
*/
|
||||
public static function dataProviderGetQueryKeys()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach ($datas as $data) {
|
||||
yield [$data];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that.
|
||||
*
|
||||
@@ -399,6 +377,28 @@ abstract class AbstractAggregatorTest extends KernelTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare date for method `testGetResultsAndLabels`.
|
||||
*/
|
||||
public static function dataProviderGetResultsAndLabels()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
if (!\is_array($datas)) {
|
||||
$datas = iterator_to_array($datas);
|
||||
}
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach ($datas as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test the `getTitle` method.
|
||||
*/
|
||||
|
@@ -33,39 +33,6 @@ abstract class AbstractExportTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
/**
|
||||
* A list of data to normalize.
|
||||
*
|
||||
* @return iterable{array}
|
||||
*/
|
||||
public static function dataProviderFormDataToNormalize(): iterable
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data, 1, []];
|
||||
}
|
||||
}
|
||||
|
||||
public static function dataProviderGetQueryKeys()
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create data for `ìnitiateQuery` method.
|
||||
*/
|
||||
public static function dataProviderInitiateQuery()
|
||||
{
|
||||
$acl = static::getAcl();
|
||||
|
||||
foreach (static::getModifiersCombination() as $modifiers) {
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$modifiers, $acl, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array usable as ACL.
|
||||
*
|
||||
@@ -182,6 +149,18 @@ abstract class AbstractExportTest extends WebTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of data to normalize.
|
||||
*
|
||||
* @return iterable{array}
|
||||
*/
|
||||
public static function dataProviderFormDataToNormalize(): iterable
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data, 1, []];
|
||||
}
|
||||
}
|
||||
|
||||
private function testOneDataNormalization(ExportInterface|DirectExportInterface $export, array $data, int $version, array $customAssert): void
|
||||
{
|
||||
$normalized = $export->normalizeFormData($data);
|
||||
@@ -262,6 +241,13 @@ abstract class AbstractExportTest extends WebTestCase
|
||||
}
|
||||
}
|
||||
|
||||
public static function dataProviderGetQueryKeys()
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that.
|
||||
*
|
||||
@@ -446,4 +432,18 @@ abstract class AbstractExportTest extends WebTestCase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create data for `ìnitiateQuery` method.
|
||||
*/
|
||||
public static function dataProviderInitiateQuery()
|
||||
{
|
||||
$acl = static::getAcl();
|
||||
|
||||
foreach (static::getModifiersCombination() as $modifiers) {
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$modifiers, $acl, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,61 +35,6 @@ abstract class AbstractFilterTest extends KernelTestCase
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* provide data for `testAliasDidNotDisappears`.
|
||||
*/
|
||||
public static function dataProviderAliasDidNotDisappears()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach ($datas as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function dataProviderAlterQuery()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach ($datas as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function dataProvideQueryExecution(): iterable
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach ($datas as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function dataProviderDescriptionAction()
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data];
|
||||
}
|
||||
}
|
||||
|
||||
protected function getUser(): User
|
||||
{
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
@@ -134,18 +79,6 @@ abstract class AbstractFilterTest extends KernelTestCase
|
||||
*/
|
||||
abstract public static function getQueryBuilders();
|
||||
|
||||
/**
|
||||
* A list of data to normalize.
|
||||
*
|
||||
* @return iterable{array}
|
||||
*/
|
||||
public static function dataProviderFormDataToNormalize(): iterable
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data, 1, []];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderFormDataToNormalize
|
||||
*/
|
||||
@@ -172,6 +105,18 @@ abstract class AbstractFilterTest extends KernelTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of data to normalize.
|
||||
*
|
||||
* @return iterable{array}
|
||||
*/
|
||||
public static function dataProviderFormDataToNormalize(): iterable
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data, 1, []];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare aliases array before and after that filter alter query.
|
||||
*
|
||||
@@ -192,6 +137,24 @@ abstract class AbstractFilterTest extends KernelTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* provide data for `testAliasDidNotDisappears`.
|
||||
*/
|
||||
public static function dataProviderAliasDidNotDisappears()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach ($datas as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test the alteration of query by the filter.
|
||||
*
|
||||
@@ -232,6 +195,21 @@ abstract class AbstractFilterTest extends KernelTestCase
|
||||
);
|
||||
}
|
||||
|
||||
public static function dataProviderAlterQuery()
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach ($datas as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProvideQueryExecution
|
||||
*/
|
||||
@@ -244,6 +222,21 @@ abstract class AbstractFilterTest extends KernelTestCase
|
||||
self::assertIsArray($actual);
|
||||
}
|
||||
|
||||
public static function dataProvideQueryExecution(): iterable
|
||||
{
|
||||
$datas = static::getFormData();
|
||||
|
||||
foreach (static::getQueryBuilders() as $qb) {
|
||||
if ([] === $datas) {
|
||||
yield [clone $qb, []];
|
||||
} else {
|
||||
foreach ($datas as $data) {
|
||||
yield [clone $qb, $data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testApplyOn()
|
||||
{
|
||||
$filter = $this->getFilter();
|
||||
@@ -308,4 +301,11 @@ abstract class AbstractFilterTest extends KernelTestCase
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function dataProviderDescriptionAction()
|
||||
{
|
||||
foreach (static::getFormData() as $data) {
|
||||
yield [$data];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -32,6 +32,17 @@ final class AddressControllerTest extends \Symfony\Bundle\FrameworkBundle\Test\W
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateAddressIds
|
||||
*/
|
||||
public function testDuplicate(int $addressId)
|
||||
{
|
||||
$this->client = $this->getClientAuthenticated();
|
||||
$this->client->request('POST', "/api/1.0/main/address/{$addressId}/duplicate.json");
|
||||
|
||||
$this->assertResponseIsSuccessful('test that duplicate is successful');
|
||||
}
|
||||
|
||||
public static function generateAddressIds(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
@@ -49,15 +60,4 @@ final class AddressControllerTest extends \Symfony\Bundle\FrameworkBundle\Test\W
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateAddressIds
|
||||
*/
|
||||
public function testDuplicate(int $addressId)
|
||||
{
|
||||
$this->client = $this->getClientAuthenticated();
|
||||
$this->client->request('POST', "/api/1.0/main/address/{$addressId}/duplicate.json");
|
||||
|
||||
$this->assertResponseIsSuccessful('test that duplicate is successful');
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,22 @@ final class AddressReferenceApiControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider provideData
|
||||
*/
|
||||
public function testSearch(int $postCodeId, string $pattern)
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request(
|
||||
'GET',
|
||||
"/api/1.0/main/address-reference/by-postal-code/{$postCodeId}/search.json",
|
||||
['q' => $pattern]
|
||||
);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public static function provideData()
|
||||
{
|
||||
self::bootKernel();
|
||||
@@ -42,20 +58,4 @@ final class AddressReferenceApiControllerTest extends WebTestCase
|
||||
|
||||
yield [$postalCode->getId(), 'rue'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideData
|
||||
*/
|
||||
public function testSearch(int $postCodeId, string $pattern)
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request(
|
||||
'GET',
|
||||
"/api/1.0/main/address-reference/by-postal-code/{$postCodeId}/search.json",
|
||||
['q' => $pattern]
|
||||
);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
}
|
||||
|
@@ -52,26 +52,6 @@ class AddressToReferenceMatcherControllerTest extends WebTestCase
|
||||
$this->assertEquals(Address::ADDR_REFERENCE_STATUS_REVIEWED, $address->getRefStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider addressUnsyncedProvider
|
||||
*/
|
||||
public function testSyncAddressWithReference(int $addressId): void
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request('POST', "/api/1.0/main/address/reference-match/{$addressId}/sync-with-reference");
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$this->addressRepository = self::getContainer()->get(AddressRepository::class);
|
||||
$address = $this->addressRepository->find($addressId);
|
||||
|
||||
$this->assertEquals(Address::ADDR_REFERENCE_STATUS_MATCH, $address->getRefStatus());
|
||||
$this->assertEquals($address->getAddressReference()->getStreet(), $address->getStreet());
|
||||
$this->assertEquals($address->getAddressReference()->getStreetNumber(), $address->getStreetNumber());
|
||||
$this->assertEquals($address->getAddressReference()->getPoint()->toWKT(), $address->getPoint()->toWKT());
|
||||
}
|
||||
|
||||
public static function addressToReviewProvider(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
@@ -98,6 +78,26 @@ class AddressToReferenceMatcherControllerTest extends WebTestCase
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider addressUnsyncedProvider
|
||||
*/
|
||||
public function testSyncAddressWithReference(int $addressId): void
|
||||
{
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request('POST', "/api/1.0/main/address/reference-match/{$addressId}/sync-with-reference");
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$this->addressRepository = self::getContainer()->get(AddressRepository::class);
|
||||
$address = $this->addressRepository->find($addressId);
|
||||
|
||||
$this->assertEquals(Address::ADDR_REFERENCE_STATUS_MATCH, $address->getRefStatus());
|
||||
$this->assertEquals($address->getAddressReference()->getStreet(), $address->getStreet());
|
||||
$this->assertEquals($address->getAddressReference()->getStreetNumber(), $address->getStreetNumber());
|
||||
$this->assertEquals($address->getAddressReference()->getPoint()->toWKT(), $address->getPoint()->toWKT());
|
||||
}
|
||||
|
||||
public static function addressUnsyncedProvider(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
|
@@ -55,6 +55,25 @@ class NewsItemControllerTest extends WebTestCase
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
public function testList()
|
||||
{
|
||||
$client = $this->getClientAuthenticated('admin', 'password');
|
||||
$client->request('GET', '/fr/admin/news_item');
|
||||
|
||||
self::assertResponseIsSuccessful('News item admin page shows');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateNewsItemIds
|
||||
*/
|
||||
public function testShowSingleItem(NewsItem $newsItem)
|
||||
{
|
||||
$client = $this->getClientAuthenticated('admin', 'password');
|
||||
$client->request('GET', "/fr/admin/news_item/{$newsItem->getId()}/view");
|
||||
|
||||
self::assertResponseIsSuccessful('Single news item admin page loads successfully');
|
||||
}
|
||||
|
||||
public static function generateNewsItemIds(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
@@ -74,23 +93,4 @@ class NewsItemControllerTest extends WebTestCase
|
||||
|
||||
yield [$newsItem];
|
||||
}
|
||||
|
||||
public function testList()
|
||||
{
|
||||
$client = $this->getClientAuthenticated('admin', 'password');
|
||||
$client->request('GET', '/fr/admin/news_item');
|
||||
|
||||
self::assertResponseIsSuccessful('News item admin page shows');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateNewsItemIds
|
||||
*/
|
||||
public function testShowSingleItem(NewsItem $newsItem)
|
||||
{
|
||||
$client = $this->getClientAuthenticated('admin', 'password');
|
||||
$client->request('GET', "/fr/admin/news_item/{$newsItem->getId()}/view");
|
||||
|
||||
self::assertResponseIsSuccessful('Single news item admin page loads successfully');
|
||||
}
|
||||
}
|
||||
|
@@ -51,27 +51,6 @@ class NewsItemsHistoryControllerTest extends WebTestCase
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
public static function generateNewsItemIds(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
|
||||
$news = new NewsItem();
|
||||
|
||||
$news->setContent('test content');
|
||||
$news->setTitle('Title');
|
||||
$news->setStartDate(new \DateTimeImmutable('yesterday'));
|
||||
|
||||
$em->persist($news);
|
||||
$em->flush();
|
||||
|
||||
static::$toDelete[] = [NewsItem::class, $news];
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
|
||||
yield [$news->getId()];
|
||||
}
|
||||
|
||||
public function testList()
|
||||
{
|
||||
self::ensureKernelShutdown();
|
||||
@@ -94,4 +73,25 @@ class NewsItemsHistoryControllerTest extends WebTestCase
|
||||
|
||||
$this->assertResponseIsSuccessful('test that single news item page loads successfully');
|
||||
}
|
||||
|
||||
public static function generateNewsItemIds(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
|
||||
$news = new NewsItem();
|
||||
|
||||
$news->setContent('test content');
|
||||
$news->setTitle('Title');
|
||||
$news->setStartDate(new \DateTimeImmutable('yesterday'));
|
||||
|
||||
$em->persist($news);
|
||||
$em->flush();
|
||||
|
||||
static::$toDelete[] = [NewsItem::class, $news];
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
|
||||
yield [$news->getId()];
|
||||
}
|
||||
}
|
||||
|
@@ -44,33 +44,6 @@ final class NotificationApiControllerTest extends WebTestCase
|
||||
self::$toDelete = [];
|
||||
}
|
||||
|
||||
public static function generateDataMarkAsRead()
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
$userRepository = self::getContainer()->get(UserRepository::class);
|
||||
$userA = $userRepository->findOneBy(['username' => 'center a_social']);
|
||||
$userB = $userRepository->findOneBy(['username' => 'center b_social']);
|
||||
|
||||
$notification = new Notification();
|
||||
$notification
|
||||
->setMessage('Test generated')
|
||||
->setRelatedEntityClass(AccompanyingPeriod::class)
|
||||
->setRelatedEntityId(0)
|
||||
->setSender($userB)
|
||||
->addAddressee($userA)
|
||||
->setUpdatedAt(new \DateTimeImmutable());
|
||||
$em->persist($notification);
|
||||
$em->refresh($notification);
|
||||
$em->flush();
|
||||
|
||||
self::$toDelete[] = [Notification::class, $notification->getId()];
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
|
||||
yield [$notification->getId()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateDataMarkAsRead
|
||||
*/
|
||||
@@ -99,4 +72,31 @@ final class NotificationApiControllerTest extends WebTestCase
|
||||
|
||||
$this->assertFalse($notification->isReadBy($user));
|
||||
}
|
||||
|
||||
public static function generateDataMarkAsRead()
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
$userRepository = self::getContainer()->get(UserRepository::class);
|
||||
$userA = $userRepository->findOneBy(['username' => 'center a_social']);
|
||||
$userB = $userRepository->findOneBy(['username' => 'center b_social']);
|
||||
|
||||
$notification = new Notification();
|
||||
$notification
|
||||
->setMessage('Test generated')
|
||||
->setRelatedEntityClass(AccompanyingPeriod::class)
|
||||
->setRelatedEntityId(0)
|
||||
->setSender($userB)
|
||||
->addAddressee($userA)
|
||||
->setUpdatedAt(new \DateTimeImmutable());
|
||||
$em->persist($notification);
|
||||
$em->refresh($notification);
|
||||
$em->flush();
|
||||
|
||||
self::$toDelete[] = [Notification::class, $notification->getId()];
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
|
||||
yield [$notification->getId()];
|
||||
}
|
||||
}
|
||||
|
@@ -24,17 +24,6 @@ final class SearchApiControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
public static function generateSearchData()
|
||||
{
|
||||
yield ['per', ['person', 'thirdparty']];
|
||||
|
||||
yield ['per', ['thirdparty']];
|
||||
|
||||
yield ['per', ['person']];
|
||||
|
||||
yield ['fjklmeqjfkdqjklrmefdqjklm', ['person', 'thirdparty']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateSearchData
|
||||
*/
|
||||
@@ -50,4 +39,15 @@ final class SearchApiControllerTest extends WebTestCase
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public static function generateSearchData()
|
||||
{
|
||||
yield ['per', ['person', 'thirdparty']];
|
||||
|
||||
yield ['per', ['thirdparty']];
|
||||
|
||||
yield ['per', ['person']];
|
||||
|
||||
yield ['fjklmeqjfkdqjklrmefdqjklm', ['person', 'thirdparty']];
|
||||
}
|
||||
}
|
||||
|
@@ -27,25 +27,6 @@ final class UserControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
public static function dataGenerateUserId()
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
/** @var UserPasswordHasherInterface::class $passwordHasher */
|
||||
$passwordHasher = self::getContainer()->get(UserPasswordHasherInterface::class);
|
||||
|
||||
$user = new User();
|
||||
$user->setUsername('Test_user '.uniqid());
|
||||
$user->setPassword($passwordHasher->hashPassword($user, 'password'));
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
|
||||
yield [$user->getId(), $user->getUsername()];
|
||||
}
|
||||
|
||||
public function testList()
|
||||
{
|
||||
$client = $this->getClientAuthenticatedAsAdmin();
|
||||
@@ -135,6 +116,25 @@ final class UserControllerTest extends WebTestCase
|
||||
$this->isPasswordValid($username, $newPassword);
|
||||
}
|
||||
|
||||
public static function dataGenerateUserId()
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
/** @var UserPasswordHasherInterface::class $passwordHasher */
|
||||
$passwordHasher = self::getContainer()->get(UserPasswordHasherInterface::class);
|
||||
|
||||
$user = new User();
|
||||
$user->setUsername('Test_user '.uniqid());
|
||||
$user->setPassword($passwordHasher->hashPassword($user, 'password'));
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
|
||||
yield [$user->getId(), $user->getUsername()];
|
||||
}
|
||||
|
||||
protected function isPasswordValid($username, $password)
|
||||
{
|
||||
/** @var \Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher $passwordEncoder */
|
||||
|
@@ -31,6 +31,22 @@ final class AgeTest extends KernelTestCase
|
||||
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateQueries
|
||||
*/
|
||||
public function testWorking(string $dql, array $args)
|
||||
{
|
||||
$dql = $this->entityManager->createQuery($dql)->setMaxResults(3);
|
||||
|
||||
foreach ($args as $key => $value) {
|
||||
$dql->setParameter($key, $value);
|
||||
}
|
||||
|
||||
$results = $dql->getResult();
|
||||
|
||||
$this->assertIsArray($results);
|
||||
}
|
||||
|
||||
public static function generateQueries(): iterable
|
||||
{
|
||||
yield [
|
||||
@@ -60,20 +76,4 @@ final class AgeTest extends KernelTestCase
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateQueries
|
||||
*/
|
||||
public function testWorking(string $dql, array $args)
|
||||
{
|
||||
$dql = $this->entityManager->createQuery($dql)->setMaxResults(3);
|
||||
|
||||
foreach ($args as $key => $value) {
|
||||
$dql->setParameter($key, $value);
|
||||
}
|
||||
|
||||
$results = $dql->getResult();
|
||||
|
||||
$this->assertIsArray($results);
|
||||
}
|
||||
}
|
||||
|
@@ -31,13 +31,6 @@ final class JsonExtractTest extends KernelTestCase
|
||||
$this->em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
}
|
||||
|
||||
public static function dataGenerateDql(): iterable
|
||||
{
|
||||
yield ['SELECT JSON_EXTRACT(c.name, \'fr\') FROM '.Country::class.' c', []];
|
||||
|
||||
yield ['SELECT JSON_EXTRACT(c.name, :lang) FROM '.Country::class.' c', ['lang' => 'fr']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataGenerateDql
|
||||
*/
|
||||
@@ -50,4 +43,11 @@ final class JsonExtractTest extends KernelTestCase
|
||||
|
||||
$this->assertIsArray($results, 'simply test that the query return a result');
|
||||
}
|
||||
|
||||
public static function dataGenerateDql(): iterable
|
||||
{
|
||||
yield ['SELECT JSON_EXTRACT(c.name, \'fr\') FROM '.Country::class.' c', []];
|
||||
|
||||
yield ['SELECT JSON_EXTRACT(c.name, :lang) FROM '.Country::class.' c', ['lang' => 'fr']];
|
||||
}
|
||||
}
|
||||
|
@@ -44,26 +44,6 @@ final class NotificationTest extends KernelTestCase
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
public static function generateNotificationData()
|
||||
{
|
||||
self::bootKernel();
|
||||
$userRepository = self::getContainer()->get(UserRepository::class);
|
||||
|
||||
$senderId = $userRepository
|
||||
->findOneBy(['username' => 'center b_social'])
|
||||
->getId();
|
||||
|
||||
$addressesIds = [];
|
||||
$addressesIds[] = $userRepository
|
||||
->findOneBy(['username' => 'center b_direction'])
|
||||
->getId();
|
||||
|
||||
yield [
|
||||
$senderId,
|
||||
$addressesIds,
|
||||
];
|
||||
}
|
||||
|
||||
public function testAddAddresseeStoreAnUread()
|
||||
{
|
||||
$notification = new Notification();
|
||||
@@ -139,4 +119,24 @@ final class NotificationTest extends KernelTestCase
|
||||
$this->assertContains($addresseeId, $unreadIds);
|
||||
}
|
||||
}
|
||||
|
||||
public static function generateNotificationData()
|
||||
{
|
||||
self::bootKernel();
|
||||
$userRepository = self::getContainer()->get(UserRepository::class);
|
||||
|
||||
$senderId = $userRepository
|
||||
->findOneBy(['username' => 'center b_social'])
|
||||
->getId();
|
||||
|
||||
$addressesIds = [];
|
||||
$addressesIds[] = $userRepository
|
||||
->findOneBy(['username' => 'center b_direction'])
|
||||
->getId();
|
||||
|
||||
yield [
|
||||
$senderId,
|
||||
$addressesIds,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -93,11 +93,10 @@ class RemoveExpiredExportGenerationCronJobTest extends TestCase
|
||||
new ExportGeneration('dummy', []),
|
||||
];
|
||||
|
||||
$repo->findExpiredExportGeneration(Argument::that(function ($dateTime) use ($clock) {
|
||||
$repo->findExpiredExportGeneration(Argument::that(fn ($dateTime) =>
|
||||
// Ensure the repository is called with the current clock time
|
||||
return $dateTime instanceof \DateTimeImmutable
|
||||
&& $dateTime->getTimestamp() === $clock->now()->getTimestamp();
|
||||
}))->willReturn($expiredExports);
|
||||
$dateTime instanceof \DateTimeImmutable
|
||||
&& $dateTime->getTimestamp() === $clock->now()->getTimestamp()))->willReturn($expiredExports);
|
||||
|
||||
// Expect one RemoveExportGenerationMessage for each expired export
|
||||
$bus->dispatch(Argument::that(fn (Envelope $envelope) => $envelope->getMessage() instanceof RemoveExportGenerationMessage))
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user