diff --git a/.changes/v2.10.0.md b/.changes/v2.10.0.md new file mode 100644 index 000000000..54f2e8cfe --- /dev/null +++ b/.changes/v2.10.0.md @@ -0,0 +1,20 @@ +## v2.10.0 - 2023-10-24 +### Feature +* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] Add a filter "grouping accompanying period by opening date" and "grouping accompanying period by closing date" +* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a filter and aggregator on accompanying period work: group/filter by handling third party +* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a filter and aggregator on activites: group/filter activities by people participating to the activities +* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a grouping on accompanying period export: group by activity type associated to at least one activity within the accompanying period +* [export] sort filters and aggregators by title +* ([#179](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/179)) [export] create a parameter that will force to skip the filtering by center (ACL) when generating an export +### Fixed +* ([#177](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/177)) [export] fix date range selection on filter and grouping "by status of the course at date", on accompanying periods + +### Résumé francophone des changements + +- Ajout d'un regroupement sur les parcours: par date de cloture et d'ouverture; +- Ajouter d'un filtre et regroupement par tiers traitant sur les actions d'accompagnement; +- ajout d'un filtre et regroupement par usager participant sur les échanges +- ajout d'un regroupement: par type d'activité associé au parcours; +- trie les filtre et regroupements par ordre alphabétique dans els exports +- ajout d'un paramètre qui permet de désactiver le filtre par centre dans les exports +- correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date" diff --git a/.changes/v2.10.1.md b/.changes/v2.10.1.md new file mode 100644 index 000000000..562b90a2c --- /dev/null +++ b/.changes/v2.10.1.md @@ -0,0 +1,3 @@ +## v2.10.1 - 2023-10-24 +### Fixed +* Fix export controller when generating an export without any data in session diff --git a/.changes/v2.10.2.md b/.changes/v2.10.2.md new file mode 100644 index 000000000..53ec97088 --- /dev/null +++ b/.changes/v2.10.2.md @@ -0,0 +1,3 @@ +## v2.10.2 - 2023-10-26 +### Fixed +* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Use injection of translator instead of ->get(). diff --git a/.changes/v2.10.3.md b/.changes/v2.10.3.md new file mode 100644 index 000000000..873c28dbb --- /dev/null +++ b/.changes/v2.10.3.md @@ -0,0 +1,3 @@ +## v2.10.3 - 2023-10-26 +### Fixed +* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Replace old method of getting translator with injection of translatorInterface diff --git a/.changes/v2.10.4.md b/.changes/v2.10.4.md new file mode 100644 index 000000000..1b421bb40 --- /dev/null +++ b/.changes/v2.10.4.md @@ -0,0 +1,3 @@ +## v2.10.4 - 2023-10-26 +### Fixed +* Fix null value constraint errors when merging relationships in doubles diff --git a/.changes/v2.10.5.md b/.changes/v2.10.5.md new file mode 100644 index 000000000..af5dc8e39 --- /dev/null +++ b/.changes/v2.10.5.md @@ -0,0 +1,4 @@ +## v2.10.5 - 2023-11-05 +### Fixed +* ([#183](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/183)) Fix "problem during download" on some filters, which used a wrong data type +* ([#184](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/184)) Fix filter "activity by date" diff --git a/.changes/v2.10.6.md b/.changes/v2.10.6.md new file mode 100644 index 000000000..7dafcca91 --- /dev/null +++ b/.changes/v2.10.6.md @@ -0,0 +1,4 @@ +## v2.10.6 - 2023-11-07 +### Fixed +* ([#182](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/182)) Fix merging of double person files. Adjustement relationship sql statement +* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix aggregator by geographical unit on person: avoid inconsistencies diff --git a/.changes/v2.11.0.md b/.changes/v2.11.0.md new file mode 100644 index 000000000..ff215159b --- /dev/null +++ b/.changes/v2.11.0.md @@ -0,0 +1,6 @@ +## v2.11.0 - 2023-11-07 +### Feature +* ([#194](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/194)) Export: add a filter "filter activity by creator job" +### Fixed +* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix "group accompanying period by geographical unit": take into account the accompanying periods when the period is not located within an unit +* Fix "group activity by creator job" aggregator diff --git a/.changes/v2.12.0.md b/.changes/v2.12.0.md new file mode 100644 index 000000000..e4e02efa0 --- /dev/null +++ b/.changes/v2.12.0.md @@ -0,0 +1,26 @@ +## v2.12.0 - 2023-11-15 +### Feature +* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add an aggregator "group activities by presence" +* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add a filter "filter activity by activity presence" +* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add an aggregator "group activities by person" (only for the activities saved in a person context) +* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add a new aggregator "group peoples by postal code" +* ([#200](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/200)) Export: split export about person on accompanying period work: one with the people associated with the work, another one with the people associated with the accompanying period +* ([#204](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/204)) Add 3 new filters and 3 new aggregators for work action creator (with jobs and scopes) + +* ([#202](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/202)) Create export for the average duration of social work actions +* ([#206](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/206)) Export: add a export which count persons on accompanying period work +* ([#206](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/206)) Export: add an export which count persons on activity +* ([#203](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/203)) Export: add clauses on the social work start date and end date within the filter "Filter accompanying period by accompanying period work" +### Fixed +* Export: fix typo in filter "filter accompanying period work on end date" +* ([#189](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/189)) Export: Fix failure in export linked to household +* ([#205](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/205)) Fix loading of accompanying period work referrers +### Traduction francophone des principaux changements +* export: ajout d'un regroupement "grouper les échanges par présence de l'usager"; +* export: ajout d'un filtre "filtre les échanges par présence de l'usager"; +* export: ajout d'un regroupement "regrouper les échanges par personne" (seulement pour les échanges enregistrés dans le contexte de l'usager); +* export: ajout d'un regroupement "grouper les usagers par codes postaux" +* export: séparation des exports sur les actions: dans l'un, les filtres des usagers portent sur les usagers concernés par l'action, dans l'autre, les filtres portent sur les usagers concernés par le parcours de l'action; +* export: ajout de 3 nouveaux filtres et regroupements sur le créateur de l'action, son métier et son service; +* export: correction de l'export sur les ménages liés aux parcours; +* correction du chargement des actions d'accompagnement diff --git a/.changes/v2.12.1.md b/.changes/v2.12.1.md new file mode 100644 index 000000000..50788fb94 --- /dev/null +++ b/.changes/v2.12.1.md @@ -0,0 +1,3 @@ +## v2.12.1 - 2023-11-16 +### Fixed +* ([#208](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/208)) Export: fix loading of form for "filter action by type, goal and result" diff --git a/.changes/v2.13.0.md b/.changes/v2.13.0.md new file mode 100644 index 000000000..cf6820115 --- /dev/null +++ b/.changes/v2.13.0.md @@ -0,0 +1,9 @@ +## v2.13.0 - 2023-11-21 +### Feature +* ([#173](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/173)) Allow user to add a phonenumber to their profile which will be included in automatically generated documents +### Fixed +* ([#211](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/211)) Export: fix loading of "Group activity by type" +* ([#190](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/190)) Export: fix loading of "group activity by reasons" +* ([#213](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/213)) Export: fix usage of some Collection returned instead of array in export filters +* ([#215](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/215)) Use only the string 'both' for gender (with a database migration) +* ([#212](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/212)) Clean the database to make working the "Group people by gender" aggregator diff --git a/.changes/v2.14.0.md b/.changes/v2.14.0.md new file mode 100644 index 000000000..560372fff --- /dev/null +++ b/.changes/v2.14.0.md @@ -0,0 +1,8 @@ +## v2.14.0 - 2023-11-24 +### Feature +* ([#161](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/161)) Export: in filter "Filter accompanying period work (social action) by type, goal and result", order the items alphabetically or with the defined order +### Fixed +* ([#141](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/141)) Export: on filter "action by type goals, and results", restore the fields when editing a saved export +* ([#219](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/219)) Export: fix the list of accompanying period work, when the "calc date" is null +* ([#222](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/222)) Fix rendering of custom fields +* Fix various errors in custom fields administration diff --git a/.changes/v2.14.1.md b/.changes/v2.14.1.md new file mode 100644 index 000000000..4400953fd --- /dev/null +++ b/.changes/v2.14.1.md @@ -0,0 +1,5 @@ +## v2.14.1 - 2023-11-29 +### Fixed +* Export: fix list person with custom fields +* ([#100](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/100)) Add a paginator to budget elements (resource and charge types) in the admin +* Fix error in ListEvaluation when "handling agents" are alone diff --git a/.changes/v2.15.0.md b/.changes/v2.15.0.md new file mode 100644 index 000000000..3c350870b --- /dev/null +++ b/.changes/v2.15.0.md @@ -0,0 +1,11 @@ +## v2.15.0 - 2023-12-11 +### Feature +* ([#191](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/191)) Add export "number of household associate with an exchange" +* ([#235](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/235)) Export: add dates on the filter "filter course by activity type" +### Fixed +* ([#214](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/214)) Fix error when posting an empty comment on an accompanying period. +* ([#233](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/233)) Fix "filter evaluation by evaluation type" (and add select2 to the list of evaluation types to pick) +* ([#234](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/234)) Fix "filter aside activity by date" + +* ([#228](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/228)) Fix export of activity for people created before the introduction of the createdAt column on person (during v1) +* ([#246](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/246)) Do not show activities, evaluations and social work when associated to a confidential accompanying period, except for the users which are allowed to see them diff --git a/.changes/v2.15.1.md b/.changes/v2.15.1.md new file mode 100644 index 000000000..aa510541d --- /dev/null +++ b/.changes/v2.15.1.md @@ -0,0 +1,5 @@ +## v2.15.1 - 2023-12-20 +### Fixed +* Fix the household export query to exclude accompanying periods that are in draft state. +### DX +* ([#167](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/167)) Fixed readthedocs compilation by updating readthedocs config file and requirements for Sphinx diff --git a/.changes/v2.15.2.md b/.changes/v2.15.2.md new file mode 100644 index 000000000..739b2482b --- /dev/null +++ b/.changes/v2.15.2.md @@ -0,0 +1,5 @@ +## v2.15.2 - 2024-01-11 +### Fixed +* Fix the id_seq used when creating a new accompanying period participation during fusion of two person files +### DX +* Set placeholder to False for expanded EntityType form fields where required is set to False. diff --git a/.changes/v2.16.0.md b/.changes/v2.16.0.md new file mode 100644 index 000000000..2668b8d5e --- /dev/null +++ b/.changes/v2.16.0.md @@ -0,0 +1,15 @@ +## v2.16.0 - 2024-02-08 +### Feature +* ([#231](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/231)) Create new filter for persons having a participation in an accompanying period during a certain time span +* ([#241](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/241)) [Export][List of accompanyign period] Add two columns: the list of persons participating to the period, and their ids +* ([#244](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/244)) Add capability to generate export about change of steps of accompanying period, and generate exports for this +* ([#253](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/253)) Export: group accompanying period by person participating +* ([#243](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/243)) Export: add filter for courses not linked to a reference address +* ([#229](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/229)) Allow to group activities linked with accompanying period by reason +* ([#115](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/115)) Prevent social work to be saved when another user edited conccurently the social work +* Modernize the event bundle, with some new fields and multiple improvements +### Fixed +* ([#220](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/220)) Fix error in logs about wrong typing of eventArgs in onEditNotificationComment method +* ([#256](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/256)) Fix the conditions upon which social actions should be optional or required in relation to social issues within the activity creation form +### UX +* ([#260](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/260)) Order list of centers alphabetically in dropdown 'user' section admin. diff --git a/.changes/v2.16.1.md b/.changes/v2.16.1.md new file mode 100644 index 000000000..185bf971e --- /dev/null +++ b/.changes/v2.16.1.md @@ -0,0 +1,3 @@ +## v2.16.1 - 2024-02-09 +### Fixed +* Force bootstrap version to avoid error in builds with newer version diff --git a/.changes/v2.16.2.md b/.changes/v2.16.2.md new file mode 100644 index 000000000..3aa01ff16 --- /dev/null +++ b/.changes/v2.16.2.md @@ -0,0 +1,3 @@ +## v2.16.2 - 2024-02-21 +### Fixed +* Check for null values in closing motive of parcours d'accompagnement for correct rendering of template diff --git a/.changes/v2.16.3.md b/.changes/v2.16.3.md new file mode 100644 index 000000000..7bb143382 --- /dev/null +++ b/.changes/v2.16.3.md @@ -0,0 +1,5 @@ +## v2.16.3 - 2024-02-26 +### Fixed +* ([#236](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/236)) Fix translation of user job -> 'service' must be 'métier' +### UX +* ([#232](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/232)) Order user jobs and services alphabetically in export filters diff --git a/.changes/v2.17.0.md b/.changes/v2.17.0.md new file mode 100644 index 000000000..52c916bcd --- /dev/null +++ b/.changes/v2.17.0.md @@ -0,0 +1,9 @@ +## v2.17.0 - 2024-03-19 +### Feature +* ([#237](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/237)) New export filter for social actions with an evaluation created between two dates +* ([#258](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/258)) In the list of accompangying period, add the list of person's centers and the duration of the course +* ([#238](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/238)) Allow to customize list person with new fields +* ([#159](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/159)) Admin can publish news on the homepage +### Fixed +* ([#264](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/264)) Fix languages: load the languages in all availables languages configured for Chill +* ([#259](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/259)) Keep a consistent behaviour between the filtering of activities within the document generation (model "accompanying period with activities"), and the same filter in the list of activities for an accompanying period diff --git a/.changes/v2.18.0.md b/.changes/v2.18.0.md new file mode 100644 index 000000000..ad52d1d87 --- /dev/null +++ b/.changes/v2.18.0.md @@ -0,0 +1,5 @@ +## v2.18.0 - 2024-03-26 +### Feature +* ([#268](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/268)) Improve admin UX to configure document templates for document generation +### Fixed +* ([#267](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/267)) Fix the join between job and user in the user list (admin): show only the current user job diff --git a/.changes/v2.18.1.md b/.changes/v2.18.1.md new file mode 100644 index 000000000..f0d68925d --- /dev/null +++ b/.changes/v2.18.1.md @@ -0,0 +1,3 @@ +## v2.18.1 - 2024-03-26 +### Fixed +* Fix layout issue in document generation for admin (minor) diff --git a/.changes/v2.18.2.md b/.changes/v2.18.2.md new file mode 100644 index 000000000..42eff9e25 --- /dev/null +++ b/.changes/v2.18.2.md @@ -0,0 +1,3 @@ +## v2.18.2 - 2024-04-12 +### Fixed +* ([#250](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/250)) Postal codes import : fix the source URL and the keys to handle each record diff --git a/.changes/v2.19.0.md b/.changes/v2.19.0.md new file mode 100644 index 000000000..6b24baa6a --- /dev/null +++ b/.changes/v2.19.0.md @@ -0,0 +1,20 @@ +## v2.19.0 - 2024-05-14 +### Feature +* ([#197](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/197)) Make the script which subscribe to microsoft calendars changes more tolerant to errors or missing configuration on the microsoft side +* ([#276](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/276)) Take closing date into account when computing the geographical unit on accompanying period. When a person moved after an accompanying period is closed, the date of closing accompanying period is took into account if it is earlier than the date given by the user. +### Fixed +* ([#270](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/270)) Fix broken link in homepage when a evaluation from a closed acc period was present in the homepage widget +* ([#275](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/275)) Allow the filter "filter accompanying period by geographical unit" to take period's location on address into account +### UX +* Form for document generation moved to the top of document list page +* ([#266](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/266)) Event bundle: adjust certain graphical issues for better user experience + + +### Traduction francophone des principaux changements + +- script de synchronisation des agendas de microsoft Outlook: le script est plus tolérant aux erreurs de configuration côté serveur (manque de droit d'accès); +- dans les statistiques sur les parcours d'accompagnements, regroupement et filtre par unité géographique: lorsque la date de prise en compte de l'adresse est postérieure à la fermeture du parcours, c'est la date de fermeture du parcours qui est prise en compte (cela permet de tenir compte de la localisation de l'usager au moment de la fermeture dans le cas où celui-ci aurait déménagé par la suite); +- sur la page d'accueil, il n'y a plus de rappel pour les évaluations pour les parcours cloturés; +- correction du filtre "filtrer par zone géographique" +- répétition du bouton pour générer un document en haut de la page "liste des documents", quand il y a plus de cinq documents; +- module événement: améliorerations graphiques diff --git a/.changes/v2.20.0.md b/.changes/v2.20.0.md new file mode 100644 index 000000000..7cfb4809c --- /dev/null +++ b/.changes/v2.20.0.md @@ -0,0 +1,21 @@ +## v2.20.0 - 2024-06-05 +### Fixed +* ([#170](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/170)) Display agents traitants instead of accompanying period referrer in export list social actions. +* Added translations for choices of durations (> 5 hours) +### Feature +* ([#145](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/145)) Allow to open documents in LibreOffice locally (need configuration within security); + + This endpoint should be added to make the endpoint works properly: + + ```yaml + security: + firewalls: + dav: + pattern: ^/dav + provider: chain_provider + stateless: true + guard: + authenticators: + - Chill\DocStoreBundle\Security\Guard\JWTOnDavUrlAuthenticator + + ``` diff --git a/.changes/v2.20.1.md b/.changes/v2.20.1.md new file mode 100644 index 000000000..5493c2c17 --- /dev/null +++ b/.changes/v2.20.1.md @@ -0,0 +1,3 @@ +## v2.20.1 - 2024-06-05 +### Fixed +* Do not allow StoredObjectCreated for edit and convert buttons diff --git a/.changes/v2.5.0.md b/.changes/v2.5.0.md new file mode 100644 index 000000000..b3f592c79 --- /dev/null +++ b/.changes/v2.5.0.md @@ -0,0 +1,39 @@ +## v2.5.0 - 2023-07-14 +### Feature +* Allow filtering on the basis of a user within general tasks lists +* ([#120](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/120)) Adding OrderFilter to the list of social actions. +* ([#125](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/125)) [export] Add a list for people with their associated course +* [export] Add ordering by person's lastname or course opening date in list which concerns accompanying course or peoples +* ([#128](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/128)) [Export] allow to group activities by localisation +* ([#129](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/129)) [export] Add a filter "filter course having an activity between two dates" +* ([#112](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/112)) [addresses] Add a cronjob to re-associate addresses with addresses reference every 6 hours +* Improve filtering layout + +### Fixed +* reimplement the visualization of all calculator results +* ([#117](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/117)) Repair my unread notification list with actions and evaluations documents +* ([#126](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/126)) Correct bug in thirdparty API search query: simplify address joins clause for child and parent kind + +### DX +* Documentation for database principles +* [cronjob] when a cronjob is executed, it may return an array of data that will be passed as argument on the next execution + +### UX +* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Better integration of address details button: look, position, title tag +* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Show address detail button on person and household banners +* Improve residential address position on show onthefly modale + +### Traduction francophone des principaux changements + +* Ajout d'un filtre "par utilisateur" aux pages de tâche +* Filtre des actions d'accompagnement par date, type, intervenant +* export: liste des usagers concernés avec détail de leurs parcours +* export: ajout d'un regroupement des échanges par localisation +* export: ajout d'un filtre "parcours ayant reçu un échange entre deux dates" +* ajout d'une tâche cron pour associer les adresses à une adresse de référence +* correction: réparation de la liste des notifications sur la page d'accueil, dans le cas où une notification concerne une action ou un document dans une évaluation +* correction: réparation de la recherche des tiers ayant des codes postaux similaires entre les parents et enfants +* meilleure intégration du bouton "détail d'une adresse": améliration de la taille et de la position +* bouton permettant de visualiser les détails d'une adresse (modale avec carte) dans la bannière "Usager" et "Ménage" +* amélioration de la modale permettant de voir les détails d'un usager: les adresses de résidence sont dans la continuité des autres adresses, et non plus dans une colonne séparée +* améliore le design et l'expérience utilisateur des filtres diff --git a/.changes/v2.5.1.md b/.changes/v2.5.1.md new file mode 100644 index 000000000..55cb3cf36 --- /dev/null +++ b/.changes/v2.5.1.md @@ -0,0 +1,3 @@ +## v2.5.1 - 2023-07-14 +### Fixed +* [collate addresses] block collating addresses to another address reference where the address reference is already the best match diff --git a/.changes/v2.5.2.md b/.changes/v2.5.2.md new file mode 100644 index 000000000..4c52b2786 --- /dev/null +++ b/.changes/v2.5.2.md @@ -0,0 +1,3 @@ +## v2.5.2 - 2023-07-15 +### Fixed +* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead) diff --git a/.changes/v2.5.3.md b/.changes/v2.5.3.md new file mode 100644 index 000000000..164a0712a --- /dev/null +++ b/.changes/v2.5.3.md @@ -0,0 +1,3 @@ +## v2.5.3 - 2023-07-20 +### Fixed +* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous. diff --git a/.changes/v2.6.0.md b/.changes/v2.6.0.md new file mode 100644 index 000000000..656373532 --- /dev/null +++ b/.changes/v2.6.0.md @@ -0,0 +1,21 @@ +## v2.6.0 - 2023-09-14 +### Feature +* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Add locations in Aside Activity. By default, suggest user location, otherwise a select with all locations. +* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Adapt Aside Activity exports: display location, filter by location, group by location +* Use the CRUD controller for center entity + add the isActive property to be able to mask instances of Center that are no longer in use. +### Fixed +* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) reinstate the fusion of duplicate persons +* Missing translation in Work Actions exports +* Reimplement the mission type filter on tasks, only for instances that have a config parameter indicating true for this. +* ([#135](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/135)) Corrects a typing error in 2 filters, which caused an +error when trying to reedit a saved export + + +* ([#136](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/136)) [household] when moving a person to a sharing position to a not-sharing position on the same household on the same date, remove the previous household membership on the same household. This fix duplicate member. +* Add missing translation for comment field placeholder in repositionning household editor. + +* Do not send an email to creator twice when adding a comment to a notification +* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) Fix gestion doublon functionality to work with chill bundles v2 +### UX +* Uniformize badge-person in household banner (background, size) + diff --git a/.changes/v2.6.1.md b/.changes/v2.6.1.md new file mode 100644 index 000000000..4907b52e9 --- /dev/null +++ b/.changes/v2.6.1.md @@ -0,0 +1,3 @@ +## v2.6.1 - 2023-09-14 +### Fixed +* Filter out active centers in exports, which uses a different PickCenterType. diff --git a/.changes/v2.6.2.md b/.changes/v2.6.2.md new file mode 100644 index 000000000..4d5b6e293 --- /dev/null +++ b/.changes/v2.6.2.md @@ -0,0 +1,3 @@ +## v2.6.2 - 2023-09-18 +### Fixed +* Fix doctrine mapping of AbstractTaskPlaceEvent and SingleTaskPlaceEvent: id property moved. diff --git a/.changes/v2.6.3.md b/.changes/v2.6.3.md new file mode 100644 index 000000000..a70634785 --- /dev/null +++ b/.changes/v2.6.3.md @@ -0,0 +1,4 @@ +## v2.6.3 - 2023-09-19 +### Fixed +* Remove id property from document +mappedsuperclass diff --git a/.changes/v2.7.0.md b/.changes/v2.7.0.md new file mode 100644 index 000000000..df389b9d3 --- /dev/null +++ b/.changes/v2.7.0.md @@ -0,0 +1,6 @@ +## v2.7.0 - 2023-09-27 +### Feature +* ([#155](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/155)) The regulation list load accompanying periods by exact postal code (address associated with postal code), and not by the content of the postal code (postal code with same code's string) +### Fixed +* ([#142](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/142)) Fix the label of filter ActivityTypeFilter to a more obvious one +* ([#140](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/140)) [export] Fix association of filter "filter location by type" which did not appears on "list of activities" diff --git a/.changes/v2.8.0.md b/.changes/v2.8.0.md new file mode 100644 index 000000000..4e0f03c11 --- /dev/null +++ b/.changes/v2.8.0.md @@ -0,0 +1,19 @@ +## v2.8.0 - 2023-10-05 + +### Feature + +* ([#162](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/162)) Reassigning list: when reassigning courses to a new user, the job associated with the course become the one of the new user (if any) +* Reassining list: the length of the list is increased to 100 courses + +### Fixed + +* ([#143](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/143)) Fix filter "accompanying course by social action" to avoid duplication in list +* ([#164](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/164)) View a third party: avoid errors when a contact has a civility +* ([#163](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/163)) Fix the filters and aggregators on exports "count peoples" +* ([#143](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/143)) From the database, avoid the creation of location history for same period and at same dates + +### Traduction francophone des principaux changements + +- Fonctionnalité: Réassigner les parcours en lot: lorsque des parcours sont réassignés "en lot", les parcours sont maintenant associés au métier du nouveau référent; +- Correction: certaines causes qui créaient des doublons dans les listes ont été corrigées; +- Correction des associations entre l'export "nombre de personnes" et les filtres et regroupements associés diff --git a/.changes/v2.9.0.md b/.changes/v2.9.0.md new file mode 100644 index 000000000..2dbf55619 --- /dev/null +++ b/.changes/v2.9.0.md @@ -0,0 +1,23 @@ +## v2.9.0 - 2023-10-17 +### Feature +* ([#147](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/147)) Add history to scopes and to jobs in administrator section. When user job or main scope of user is changed, automaticaly add a new row in history. +* ([#146](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/146)) Allow closing motives to be identified as 'canceling the accompanying period' + don't take canceled accompanying periods into account +* [export] add an aggregator for activities: group by job scope's creator aggregator +* DX: prepare the code for the upgrade to symfony 5.4 + +### Traductions francophones des principaux changements + +- ajout de l'historique des services et métiers pour les utilisateurs. Les exports, filtres et regroupements sont adaptés pour tenir compte du métier et du service + de l'utilisateur au moment de l'échange, de sa désignation comme agent traitant de l'échange ou du moment du rendez-vous ([#147](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/147))) +- modification des motifs de cloture des parcours: ajout d'un chanmp "annule le parcours", qui permet d'indiquer que le motif "annule" le parcours. Les parcours annulés n'apparaissent + pas dans les statistiques +- ajouter d'un regroupement pour les échanges: grouper par métier et service du créateur de l'échange + + +### Possible BC break in configuration + +This release remove the use of deprecated package [symfony/templating](https://symfony.com/components/Templating). + +If you use this package in your own bundle (usually `src/` directory, or other dependencies), you should add this dependencies in your local composer.json (`composer require symfony/templating`). + +But if you do not need this any more, you must ensure that the configuration key `framework.templating` is removed. This is usually located into `config/packages/framework.yaml`. [See here an example](https://gitea.champs-libres.be/Chill-project/chill-skeleton-basic/commit/cc716beaecc239e6a189f3db62ea95f169a37505#diff-df607fe73ff82c569824a7392edf5e760e998efe) diff --git a/.changes/v2.9.1.md b/.changes/v2.9.1.md new file mode 100644 index 000000000..2e0ddc3ab --- /dev/null +++ b/.changes/v2.9.1.md @@ -0,0 +1,3 @@ +## v2.9.1 - 2023-10-17 +### Fixed +* Fix the handling of activity form when editing or creating an activity in an accompanying period with multiple centers diff --git a/.changes/v2.9.2.md b/.changes/v2.9.2.md new file mode 100644 index 000000000..493606b80 --- /dev/null +++ b/.changes/v2.9.2.md @@ -0,0 +1,3 @@ +## v2.9.2 - 2023-10-17 +### Fixed +* Fix possible null values in string's entities diff --git a/.changie.yaml b/.changie.yaml index 8a25ed695..0145062f8 100644 --- a/.changie.yaml +++ b/.changie.yaml @@ -7,7 +7,7 @@ versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}' kindFormat: '### {{.Kind}}' # Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description. changeFormat: >- - * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if not (eq .Custom.Long "") }} + * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if and (.Custom.Long) (not (eq .Custom.Long "")) }} {{ .Custom.Long }}{{ end }} custom: @@ -30,6 +30,8 @@ kinds: auto: patch - label: DX auto: patch + - label: UX + auto: patch newlines: afterChangelogHeader: 1 beforeChangelogVersion: 1 diff --git a/.editorconfig b/.editorconfig index a3e5a0fc1..bede621e3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,3 +23,7 @@ max_line_length = 0 indent_size = 2 indent_style = space +[.rst] +ident_size = 3 +ident_style = space + diff --git a/.env.test b/.env.test index 914deb541..9245579c0 100644 --- a/.env.test +++ b/.env.test @@ -3,3 +3,39 @@ # Run tests from root to adapt your own environment KERNEL_CLASS='App\Kernel' APP_SECRET='$ecretf0rt3st' + +ADMIN_PASSWORD=password + +LOCALE=fr +REDIS_URL=redis +REDIS_PORT=6379 +REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT} + +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +JWT_PASSPHRASE=2a30f6ba26521a2613821da35f28386e + +TWILIO_SID=~ +TWILIO_SECRET=~ +DEFAULT_CARRIER_CODE=BE + +ADD_ADDRESS_DEFAULT_COUNTRY=BE + +ADD_ADDRESS_MAP_CENTER_X=50.8443 +ADD_ADDRESS_MAP_CENTER_Y=4.3523 +ADD_ADDRESS_MAP_CENTER_Z=15 + +SHORT_MESSAGE_DSN=null://null +MESSENGER_TRANSPORT_DSN=sync:// +###< symfony/messenger ### + +###> doctrine/doctrine-bundle ### +# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml +# +DATABASE_URL="postgresql://postgres:postgres@db:5432/test?serverVersion=14&charset=utf8" +###< doctrine/doctrine-bundle ### + +ASYNC_UPLOAD_TEMP_URL_KEY= +ASYNC_UPLOAD_TEMP_URL_BASE_PATH= +ASYNC_UPLOAD_TEMP_URL_CONTAINER= diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aa1eff8b1..43f687649 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ # Select what we should cache between builds cache: paths: - - tests/app/vendor/ + - /vendor/ - .cache # Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service @@ -11,6 +11,10 @@ cache: services: - name: postgis/postgis:14-3.3-alpine alias: db + command: + - postgres + - "-c" + - max_connections=1000 - name: redis alias: redis @@ -23,13 +27,15 @@ variables: # configure database access DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres?serverVersion=14&charset=utf8 # fetch the chill-app using git submodules - GIT_SUBMODULE_STRATEGY: recursive + # GIT_SUBMODULE_STRATEGY: recursive REDIS_HOST: redis REDIS_PORT: 6379 REDIS_URL: redis://redis:6379 - # change vendor dir to make the app install into tests/apps - COMPOSER_VENDOR_DIR: tests/app/vendor DEFAULT_CARRIER_CODE: BE + # force a timezone + TZ: Europe/Brussels + # avoid direct deprecations (using symfony phpunit bridge: https://symfony.com/doc/4.x/components/phpunit_bridge.html#internal-deprecations + SYMFONY_DEPRECATIONS_HELPER: max[total]=99999999&max[self]=0&max[direct]=0&verbose=1 stages: - Composer install @@ -47,10 +53,10 @@ build: paths: - .cache/ artifacts: - expire_in: 30 min + expire_in: 1 day paths: - bin - - tests/app/vendor/ + - vendor/ code_style: stage: Tests @@ -61,10 +67,10 @@ code_style: paths: - .cache/ artifacts: - expire_in: 30 min + expire_in: 1 day paths: - bin - - tests/app/vendor/ + - vendor/ phpstan_tests: stage: Tests @@ -75,24 +81,25 @@ phpstan_tests: paths: - .cache/ artifacts: - expire_in: 30 min + expire_in: 1 day paths: - bin - - tests/app/vendor/ + - vendor/ rector_tests: stage: Tests image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 script: - - bin/rector --dry-run + - tests/console cache:clear + - bin/rector process --dry-run cache: paths: - .cache/ artifacts: - expire_in: 30 min + expire_in: 1 day paths: - bin - - tests/app/vendor/ + - vendor/ # psalm_tests: # stage: Tests @@ -109,19 +116,17 @@ rector_tests: unit_tests: stage: Tests image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 - # until we fix testes - allow_failure: true script: - - php tests/app/bin/console doctrine:migrations:migrate -n - - php -d memory_limit=2G tests/app/bin/console cache:clear --env=dev - - php -d memory_limit=3G tests/app/bin/console doctrine:fixtures:load -n - - php -d memory_limit=2G tests/app/bin/console cache:clear --env=test - - php -d memory_limit=4G bin/phpunit --colors=never + - php tests/console doctrine:migrations:migrate -n --env=test + - php tests/console chill:db:sync-views --env=test + - php -d memory_limit=2G tests/console cache:clear --env=test + - php -d memory_limit=3G tests/console doctrine:fixtures:load -n + - php -d memory_limit=4G bin/phpunit --colors=never --exclude-group dbIntensive artifacts: - expire_in: 30 min + expire_in: 1 day paths: - bin - - tests/app/vendor/ + - vendor/ release: stage: Deploy diff --git a/.gitmodules b/.gitmodules index 560ba7980..7bc519c88 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "_exts/sphinx-php"] path = _exts/sphinx-php url = https://github.com/fabpot/sphinx-php.git -[submodule "tests/app"] - path = tests/app - url = https://gitlab.com/Chill-projet/chill-app.git diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 31d64e600..9aae1c43f 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -91,7 +91,7 @@ $rules = array_merge( [ '@PhpCsFixer' => true, '@PhpCsFixer:risky' => false, - '@Symfony' => false, + '@Symfony' => true, '@Symfony:risky' => false, 'ordered_class_elements' => [ 'order' => [ @@ -111,13 +111,13 @@ $rules = array_merge( 'method_private', ], 'sort_algorithm' => 'alpha', - ] + ], ], $rules, $riskyRules, $untilFullSwitchToPhp8, ); -$rules['header_comment']['header'] = trim(file_get_contents(__DIR__ . '/resource/header.txt')); +$rules['header_comment']['header'] = trim(file_get_contents(__DIR__.'/resource/header.txt')); return $config->setRules($rules); diff --git a/.readthedocs.yml b/.readthedocs.yml index 3b5a7def9..cd8f36eba 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,10 +1,14 @@ --- version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.7" + sphinx: configuration: docs/source/conf.py python: - version: 3.7 install: - - requirements: docs/requirements.txt + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b74eea58d..93c606250 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,387 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## v2.20.1 - 2024-06-05 +### Fixed +* Do not allow StoredObjectCreated for edit and convert buttons + +## v2.20.0 - 2024-06-05 +### Fixed +* ([#170](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/170)) Display agents traitants instead of accompanying period referrer in export list social actions. +* Added translations for choices of durations (> 5 hours) +### Feature +* ([#145](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/145)) Allow to open documents in LibreOffice locally (need configuration within security); + + This endpoint should be added to make the endpoint works properly: + + ```yaml + security: + firewalls: + dav: + pattern: ^/dav + provider: chain_provider + stateless: true + guard: + authenticators: + - Chill\DocStoreBundle\Security\Guard\JWTOnDavUrlAuthenticator + + ``` + +## v2.19.0 - 2024-05-14 +### Feature +* ([#197](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/197)) Make the script which subscribe to microsoft calendars changes more tolerant to errors or missing configuration on the microsoft side +* ([#276](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/276)) Take closing date into account when computing the geographical unit on accompanying period. When a person moved after an accompanying period is closed, the date of closing accompanying period is took into account if it is earlier than the date given by the user. +### Fixed +* ([#270](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/270)) Fix broken link in homepage when a evaluation from a closed acc period was present in the homepage widget +* ([#275](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/275)) Allow the filter "filter accompanying period by geographical unit" to take period's location on address into account +### UX +* Form for document generation moved to the top of document list page +* ([#266](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/266)) Event bundle: adjust certain graphical issues for better user experience + + +### Traduction francophone des principaux changements + +- script de synchronisation des agendas de microsoft Outlook: le script est plus tolérant aux erreurs de configuration côté serveur (manque de droit d'accès); +- dans les statistiques sur les parcours d'accompagnements, regroupement et filtre par unité géographique: lorsque la date de prise en compte de l'adresse est postérieure à la fermeture du parcours, c'est la date de fermeture du parcours qui est prise en compte (cela permet de tenir compte de la localisation de l'usager au moment de la fermeture dans le cas où celui-ci aurait déménagé par la suite); +- sur la page d'accueil, il n'y a plus de rappel pour les évaluations pour les parcours cloturés; +- correction du filtre "filtrer par zone géographique" +- répétition du bouton pour générer un document en haut de la page "liste des documents", quand il y a plus de cinq documents; +- module événement: améliorerations graphiques + +## v2.18.2 - 2024-04-12 +### Fixed +* ([#250](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/250)) Postal codes import : fix the source URL and the keys to handle each record + +## v2.18.1 - 2024-03-26 +### Fixed +* Fix layout issue in document generation for admin (minor) + +## v2.18.0 - 2024-03-26 +### Feature +* ([#268](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/268)) Improve admin UX to configure document templates for document generation +### Fixed +* ([#267](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/267)) Fix the join between job and user in the user list (admin): show only the current user job + +## v2.17.0 - 2024-03-19 +### Feature +* ([#237](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/237)) New export filter for social actions with an evaluation created between two dates +* ([#258](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/258)) In the list of accompangying period, add the list of person's centers and the duration of the course +* ([#238](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/238)) Allow to customize list person with new fields +* ([#159](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/159)) Admin can publish news on the homepage +### Fixed +* ([#264](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/264)) Fix languages: load the languages in all availables languages configured for Chill +* ([#259](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/259)) Keep a consistent behaviour between the filtering of activities within the document generation (model "accompanying period with activities"), and the same filter in the list of activities for an accompanying period + +## v2.16.3 - 2024-02-26 +### Fixed +* ([#236](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/236)) Fix translation of user job -> 'service' must be 'métier' +### UX +* ([#232](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/232)) Order user jobs and services alphabetically in export filters + +## v2.16.2 - 2024-02-21 +### Fixed +* Check for null values in closing motive of parcours d'accompagnement for correct rendering of template + +## v2.16.1 - 2024-02-09 +### Fixed +* Force bootstrap version to avoid error in builds with newer version + +## v2.16.0 - 2024-02-08 +### Feature +* ([#231](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/231)) Create new filter for persons having a participation in an accompanying period during a certain time span +* ([#241](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/241)) [Export][List of accompanyign period] Add two columns: the list of persons participating to the period, and their ids +* ([#244](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/244)) Add capability to generate export about change of steps of accompanying period, and generate exports for this +* ([#253](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/253)) Export: group accompanying period by person participating +* ([#243](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/243)) Export: add filter for courses not linked to a reference address +* ([#229](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/229)) Allow to group activities linked with accompanying period by reason +* ([#115](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/115)) Prevent social work to be saved when another user edited conccurently the social work +* Modernize the event bundle, with some new fields and multiple improvements +### Fixed +* ([#220](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/220)) Fix error in logs about wrong typing of eventArgs in onEditNotificationComment method +* ([#256](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/256)) Fix the conditions upon which social actions should be optional or required in relation to social issues within the activity creation form +### UX +* ([#260](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/260)) Order list of centers alphabetically in dropdown 'user' section admin. + +## v2.15.2 - 2024-01-11 +### Fixed +* Fix the id_seq used when creating a new accompanying period participation during fusion of two person files +### DX +* Set placeholder to False for expanded EntityType form fields where required is set to False. + +## v2.15.1 - 2023-12-20 +### Fixed +* Fix the household export query to exclude accompanying periods that are in draft state. +### DX +* ([#167](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/167)) Fixed readthedocs compilation by updating readthedocs config file and requirements for Sphinx + +## v2.15.0 - 2023-12-11 +### Feature +* ([#191](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/191)) Add export "number of household associate with an exchange" +* ([#235](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/235)) Export: add dates on the filter "filter course by activity type" +### Fixed +* ([#214](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/214)) Fix error when posting an empty comment on an accompanying period. +* ([#233](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/233)) Fix "filter evaluation by evaluation type" (and add select2 to the list of evaluation types to pick) +* ([#234](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/234)) Fix "filter aside activity by date" + +* ([#228](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/228)) Fix export of activity for people created before the introduction of the createdAt column on person (during v1) +* ([#246](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/246)) Do not show activities, evaluations and social work when associated to a confidential accompanying period, except for the users which are allowed to see them + +## v2.14.1 - 2023-11-29 +### Fixed +* Export: fix list person with custom fields +* ([#100](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/100)) Add a paginator to budget elements (resource and charge types) in the admin +* Fix error in ListEvaluation when "handling agents" are alone + +## v2.14.0 - 2023-11-24 +### Feature +* ([#161](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/161)) Export: in filter "Filter accompanying period work (social action) by type, goal and result", order the items alphabetically or with the defined order +### Fixed +* ([#141](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/141)) Export: on filter "action by type goals, and results", restore the fields when editing a saved export +* ([#219](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/219)) Export: fix the list of accompanying period work, when the "calc date" is null +* ([#222](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/222)) Fix rendering of custom fields +* Fix various errors in custom fields administration + +## v2.13.0 - 2023-11-21 +### Feature +* ([#173](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/173)) Allow user to add a phonenumber to their profile which will be included in automatically generated documents +### Fixed +* ([#211](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/211)) Export: fix loading of "Group activity by type" +* ([#190](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/190)) Export: fix loading of "group activity by reasons" +* ([#213](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/213)) Export: fix usage of some Collection returned instead of array in export filters +* ([#215](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/215)) Use only the string 'both' for gender (with a database migration) +* ([#212](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/212)) Clean the database to make working the "Group people by gender" aggregator + +## v2.12.1 - 2023-11-16 +### Fixed +* ([#208](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/208)) Export: fix loading of form for "filter action by type, goal and result" + +## v2.12.0 - 2023-11-15 +### Feature +* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add an aggregator "group activities by presence" +* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add a filter "filter activity by activity presence" +* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add an aggregator "group activities by person" (only for the activities saved in a person context) +* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add a new aggregator "group peoples by postal code" +* ([#200](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/200)) Export: split export about person on accompanying period work: one with the people associated with the work, another one with the people associated with the accompanying period +* ([#204](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/204)) Add 3 new filters and 3 new aggregators for work action creator (with jobs and scopes) + +* ([#202](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/202)) Create export for the average duration of social work actions +* ([#206](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/206)) Export: add a export which count persons on accompanying period work +* ([#206](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/206)) Export: add an export which count persons on activity +* ([#203](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/203)) Export: add clauses on the social work start date and end date within the filter "Filter accompanying period by accompanying period work" +### Fixed +* Export: fix typo in filter "filter accompanying period work on end date" +* ([#189](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/189)) Export: Fix failure in export linked to household +* ([#205](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/205)) Fix loading of accompanying period work referrers +### Traduction francophone des principaux changements +* export: ajout d'un regroupement "grouper les échanges par présence de l'usager"; +* export: ajout d'un filtre "filtre les échanges par présence de l'usager"; +* export: ajout d'un regroupement "regrouper les échanges par personne" (seulement pour les échanges enregistrés dans le contexte de l'usager); +* export: ajout d'un regroupement "grouper les usagers par codes postaux" +* export: séparation des exports sur les actions: dans l'un, les filtres des usagers portent sur les usagers concernés par l'action, dans l'autre, les filtres portent sur les usagers concernés par le parcours de l'action; +* export: ajout de 3 nouveaux filtres et regroupements sur le créateur de l'action, son métier et son service; +* export: correction de l'export sur les ménages liés aux parcours; +* correction du chargement des actions d'accompagnement + +## v2.11.0 - 2023-11-07 +### Feature +* ([#194](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/194)) Export: add a filter "filter activity by creator job" +### Fixed +* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix "group accompanying period by geographical unit": take into account the accompanying periods when the period is not located within an unit +* Fix "group activity by creator job" aggregator + +## v2.10.6 - 2023-11-07 +### Fixed +* ([#182](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/182)) Fix merging of double person files. Adjustement relationship sql statement +* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix aggregator by geographical unit on person: avoid inconsistencies + +## v2.10.5 - 2023-11-05 +### Fixed +* ([#183](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/183)) Fix "problem during download" on some filters, which used a wrong data type +* ([#184](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/184)) Fix filter "activity by date" + +## v2.10.4 - 2023-10-26 +### Fixed +* Fix null value constraint errors when merging relationships in doubles + +## v2.10.3 - 2023-10-26 +### Fixed +* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Replace old method of getting translator with injection of translatorInterface + +## v2.10.2 - 2023-10-26 +### Fixed +* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Use injection of translator instead of ->get(). + +## v2.10.1 - 2023-10-24 +### Fixed +* Fix export controller when generating an export without any data in session + +## v2.10.0 - 2023-10-24 +### Feature +* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] Add a filter "grouping accompanying period by opening date" and "grouping accompanying period by closing date" +* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a filter and aggregator on accompanying period work: group/filter by handling third party +* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a filter and aggregator on activites: group/filter activities by people participating to the activities +* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a grouping on accompanying period export: group by activity type associated to at least one activity within the accompanying period +* [export] sort filters and aggregators by title +* ([#179](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/179)) [export] create a parameter that will force to skip the filtering by center (ACL) when generating an export +### Fixed +* ([#177](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/177)) [export] fix date range selection on filter and grouping "by status of the course at date", on accompanying periods + +### Résumé francophone des changements + +- Ajout d'un regroupement sur les parcours: par date de cloture et d'ouverture; +- Ajouter d'un filtre et regroupement par tiers traitant sur les actions d'accompagnement; +- ajout d'un filtre et regroupement par usager participant sur les échanges +- ajout d'un regroupement: par type d'activité associé au parcours; +- trie les filtre et regroupements par ordre alphabétique dans els exports +- ajout d'un paramètre qui permet de désactiver le filtre par centre dans les exports +- correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date" + +## v2.9.2 - 2023-10-17 +### Fixed +* Fix possible null values in string's entities + +## v2.9.1 - 2023-10-17 +### Fixed +* Fix the handling of activity form when editing or creating an activity in an accompanying period with multiple centers + +## v2.9.0 - 2023-10-17 +### Feature +* ([#147](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/147)) Add history to scopes and to jobs in administrator section. When user job or main scope of user is changed, automaticaly add a new row in history. +* ([#146](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/146)) Allow closing motives to be identified as 'canceling the accompanying period' + don't take canceled accompanying periods into account +* [export] add an aggregator for activities: group by job scope's creator aggregator +* DX: prepare the code for the upgrade to symfony 5.4 + +### Traductions francophones des principaux changements + +- ajout de l'historique des services et métiers pour les utilisateurs. Les exports, filtres et regroupements sont adaptés pour tenir compte du métier et du service + de l'utilisateur au moment de l'échange, de sa désignation comme agent traitant de l'échange ou du moment du rendez-vous ([#147](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/147))) +- modification des motifs de cloture des parcours: ajout d'un chanmp "annule le parcours", qui permet d'indiquer que le motif "annule" le parcours. Les parcours annulés n'apparaissent + pas dans les statistiques +- ajouter d'un regroupement pour les échanges: grouper par métier et service du créateur de l'échange + + +### Possible BC break in configuration + +This release remove the use of deprecated package [symfony/templating](https://symfony.com/components/Templating). + +If you use this package in your own bundle (usually `src/` directory, or other dependencies), you should add this dependencies in your local composer.json (`composer require symfony/templating`). + +But if you do not need this any more, you must ensure that the configuration key `framework.templating` is removed. This is usually located into `config/packages/framework.yaml`. [See here an example](https://gitea.champs-libres.be/Chill-project/chill-skeleton-basic/commit/cc716beaecc239e6a189f3db62ea95f169a37505#diff-df607fe73ff82c569824a7392edf5e760e998efe) + +## v2.8.0 - 2023-10-05 + +### Feature + +* ([#162](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/162)) Reassigning list: when reassigning courses to a new user, the job associated with the course become the one of the new user (if any) +* Reassining list: the length of the list is increased to 100 courses + +### Fixed + +* ([#143](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/143)) Fix filter "accompanying course by social action" to avoid duplication in list +* ([#164](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/164)) View a third party: avoid errors when a contact has a civility +* ([#163](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/163)) Fix the filters and aggregators on exports "count peoples" +* ([#143](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/143)) From the database, avoid the creation of location history for same period and at same dates + +### Traduction francophone des principaux changements + +- Fonctionnalité: Réassigner les parcours en lot: lorsque des parcours sont réassignés "en lot", les parcours sont maintenant associés au métier du nouveau référent; +- Correction: certaines causes qui créaient des doublons dans les listes ont été corrigées; +- Correction des associations entre l'export "nombre de personnes" et les filtres et regroupements associés + +## v2.7.0 - 2023-09-27 +### Feature +* ([#155](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/155)) The regulation list load accompanying periods by exact postal code (address associated with postal code), and not by the content of the postal code (postal code with same code's string) +### Fixed +* ([#142](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/142)) Fix the label of filter ActivityTypeFilter to a more obvious one +* ([#140](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/140)) [export] Fix association of filter "filter location by type" which did not appears on "list of activities" + +## v2.6.3 - 2023-09-19 +### Fixed +* Remove id property from document +mappedsuperclass + +## v2.6.2 - 2023-09-18 +### Fixed +* Fix doctrine mapping of AbstractTaskPlaceEvent and SingleTaskPlaceEvent: id property moved. + +## v2.6.1 - 2023-09-14 +### Fixed +* Filter out active centers in exports, which uses a different PickCenterType. + +## v2.6.0 - 2023-09-14 +### Feature +* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Add locations in Aside Activity. By default, suggest user location, otherwise a select with all locations. +* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Adapt Aside Activity exports: display location, filter by location, group by location +* Use the CRUD controller for center entity + add the isActive property to be able to mask instances of Center that are no longer in use. +### Fixed +* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) reinstate the fusion of duplicate persons +* Missing translation in Work Actions exports +* Reimplement the mission type filter on tasks, only for instances that have a config parameter indicating true for this. +* ([#135](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/135)) Corrects a typing error in 2 filters, which caused an +error when trying to reedit a saved export + + +* ([#136](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/136)) [household] when moving a person to a sharing position to a not-sharing position on the same household on the same date, remove the previous household membership on the same household. This fix duplicate member. +* Add missing translation for comment field placeholder in repositionning household editor. + +* Do not send an email to creator twice when adding a comment to a notification +* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) Fix gestion doublon functionality to work with chill bundles v2 +### UX +* Uniformize badge-person in household banner (background, size) + + +## v2.5.3 - 2023-07-20 +### Fixed +* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous. + +## v2.5.2 - 2023-07-15 +### Fixed +* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead) + +## v2.5.1 - 2023-07-14 +### Fixed +* [collate addresses] block collating addresses to another address reference where the address reference is already the best match + +## v2.5.0 - 2023-07-14 +### Feature +* Allow filtering on the basis of a user within general tasks lists +* ([#120](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/120)) Adding OrderFilter to the list of social actions. +* ([#125](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/125)) [export] Add a list for people with their associated course +* [export] Add ordering by person's lastname or course opening date in list which concerns accompanying course or peoples +* ([#128](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/128)) [Export] allow to group activities by localisation +* ([#129](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/129)) [export] Add a filter "filter course having an activity between two dates" +* ([#112](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/112)) [addresses] Add a cronjob to re-associate addresses with addresses reference every 6 hours +* Improve filtering layout + +### Fixed +* reimplement the visualization of all calculator results +* ([#117](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/117)) Repair my unread notification list with actions and evaluations documents +* ([#126](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/126)) Correct bug in thirdparty API search query: simplify address joins clause for child and parent kind + +### DX +* Documentation for database principles +* [cronjob] when a cronjob is executed, it may return an array of data that will be passed as argument on the next execution + +### UX +* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Better integration of address details button: look, position, title tag +* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Show address detail button on person and household banners +* Improve residential address position on show onthefly modale + +### Traduction francophone des principaux changements + +* Ajout d'un filtre "par utilisateur" aux pages de tâche +* Filtre des actions d'accompagnement par date, type, intervenant +* export: liste des usagers concernés avec détail de leurs parcours +* export: ajout d'un regroupement des échanges par localisation +* export: ajout d'un filtre "parcours ayant reçu un échange entre deux dates" +* ajout d'une tâche cron pour associer les adresses à une adresse de référence +* correction: réparation de la liste des notifications sur la page d'accueil, dans le cas où une notification concerne une action ou un document dans une évaluation +* correction: réparation de la recherche des tiers ayant des codes postaux similaires entre les parents et enfants +* meilleure intégration du bouton "détail d'une adresse": améliration de la taille et de la position +* bouton permettant de visualiser les détails d'une adresse (modale avec carte) dans la bannière "Usager" et "Ménage" +* amélioration de la modale permettant de voir les détails d'un usager: les adresses de résidence sont dans la continuité des autres adresses, et non plus dans une colonne séparée +* améliore le design et l'expérience utilisateur des filtres + ## v2.4.0 - 2023-07-07 ### Feature diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..fe34130d2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing + +Chill is an open source, community-driven project. + +If you'd like to contribute, please read the following. + +## What can you do ? + +Chill is an open-source project driven by a community of developers, users and social workers. If you don't feel ready to contribute code or patches, reviewing issues and pull requests (PRs) can be a great start to get involved and give back. + +If you don't have your own instance or don't want to use it, you can try to reproduce bugs using the instance https://demo.chill.social + +## Core team + +The core team is the group of developers that determine the direction and evolution of the Chill project. Their votes rule if the features and patches proposed by the community are approved or rejected. + +All the Chill Core members are long-time contributors with solid technical expertise and they have demonstrated a strong commitment to drive the project forward. + +The core team: + +- elects his own members; +- merge pull requests; + +### members + +Project leader: [julienfastre](https://gitlab.com/julienfastre) + +Core members: + +- [tchama](https://gitlab.com/tchama) +- [LenaertsJ](https://gitlab.com/LenaertsJ) +- [nobohan](https://gitlab.com/nobohan) + +### Becoming a project member + +About once a year, the core team discusses the opportunity to invite new members. To become a core team member, you must: + +- take part on the development for at least 6 month: propose multiple merge requests and participate to the peer review process; +- through this participation, demonstrate your technical skills and your knowledge of the software and any of their dependencies; + +### Core Membership Revocation + +A Chill Core membership can be revoked for any of the following reasons: + +- Refusal to follow the rules and policies stated in this document; +- Lack of activity for the past six months; +- Willful negligence or intent to harm the Chill project; + +The decision is taken by the majority of project members. + +## Code development rules + +### Merge requests + +Every merge request must contains: + +- one more entries suitable for generating a changelog. This is done using the [changie utility](https://changie.dev); +- a comprehensible description of the changes; +- if applicable, automated tests should be adapted or created; +- the code style must pass the project's rules, and non phpstan errors must be raised nor rector refactoring suggestion. + +The pipelines must pass. + +In case of emergency, some rules may be temporarily ignored. + +### Merge Request Voting Policy + +- -1 votes must always be justified by technical and objective reasons; +- +1 (technically: approbation on the merge request) votes do not require justification, unless there is at least one -1 vote; +- Core members can change their votes as many times as they desire during the course of a merge request discussion; +- Core members are not allowed to vote on their own merge requests. + +### Merge Request Merging Process + +All code must be committed to the repository through merge requests, except for minor changes which can be committed directly to the repository. + +### Release Policy + +The Core members are also the release manager for every Chill version. + diff --git a/composer.json b/composer.json index a128b1b77..37282788e 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "social worker" ], "require": { - "php": "^7.4|^8.2", + "php": "^8.2", + "ext-dom": "*", "ext-json": "*", "ext-openssl": "*", "ext-redis": "*", @@ -48,7 +49,6 @@ "symfony/monolog-bundle": "^3.5", "symfony/security-bundle": "^4.4", "symfony/serializer": "^5.3", - "symfony/templating": "^4.4", "symfony/translation": "^4.4", "symfony/twig-bundle": "^4.4", "symfony/validator": "^4.4", @@ -76,7 +76,7 @@ "phpunit/phpunit": ">= 7.5", "psalm/plugin-phpunit": "^0.18.4", "psalm/plugin-symfony": "^4.0.2", - "rector/rector": "^0.15.23", + "rector/rector": "^1.1.0", "symfony/debug-bundle": "^5.1", "symfony/dotenv": "^4.4", "symfony/maker-bundle": "^1.20", @@ -98,7 +98,6 @@ "Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle", "Chill\\DocStoreBundle\\": "src/Bundle/ChillDocStoreBundle", "Chill\\EventBundle\\": "src/Bundle/ChillEventBundle", - "Chill\\FamilyMemberBundle\\": "src/Bundle/ChillFamilyMemberBundle", "Chill\\MainBundle\\": "src/Bundle/ChillMainBundle", "Chill\\PersonBundle\\": "src/Bundle/ChillPersonBundle", "Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle", @@ -110,7 +109,7 @@ }, "autoload-dev": { "psr-4": { - "App\\": "tests/app/src/", + "App\\": "tests/", "Chill\\DocGeneratorBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests", "Chill\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests", "Chill\\Utils\\Rector\\Tests\\": "utils/rector/tests" @@ -123,16 +122,15 @@ "ocramius/package-versions": true, "phpro/grumphp": true, "phpstan/extension-installer": true, - "roave/you-are-using-it-wrong": true + "roave/you-are-using-it-wrong": true, + "symfony/runtime": true }, "bin-dir": "bin", "optimize-autoloader": true, - "sort-packages": true, - "vendor-dir": "tests/app/vendor" + "sort-packages": true }, "scripts": { "auto-scripts": { - "assets:install %PUBLIC_DIR%": "symfony-cmd", "cache:clear": "symfony-cmd" } } diff --git a/docs/README.md b/docs/README.md index 98b199f02..8ae7f650a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,7 +27,7 @@ To compile this documentation : Contribute =========== -Issue tracker : https://git.framasoft.org/groups/Chill-project/issues +Issue tracker : https://gitlab.com/Chill-Projet/chill-bundles/-/issues Licence ======= diff --git a/docs/requirements.txt b/docs/requirements.txt index 26a019bfa..a8d979a2b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,7 @@ docutils==0.13.1 Pygments==2.2.0 sphinx==1.8.5 +Jinja2<3.1 git+https://github.com/fabpot/sphinx-php.git@v2.0.2#egg_name=sphinx-php jsx-lexer===0.0.8 sphinx_rtd_theme==0.5.0 diff --git a/docs/source/_static/code/exports/CountPerson.php b/docs/source/_static/code/exports/CountPerson.php index be800e52c..a0f6931ac 100644 --- a/docs/source/_static/code/exports/CountPerson.php +++ b/docs/source/_static/code/exports/CountPerson.php @@ -54,18 +54,9 @@ class CountPerson implements ExportInterface public function getLabels($key, array $values, $data) { // the Closure which will be executed by the formatter. - return function ($value) { - switch ($value) { - case '_header': - // we have to process specifically the '_header' string, - // which will be used by the formatter to show a column title - return $this->getTitle(); - - default: - // for all value, we do not process them and return them - // immediatly - return $value; - } + return fn($value) => match ($value) { + '_header' => $this->getTitle(), + default => $value, }; } @@ -94,7 +85,7 @@ class CountPerson implements ExportInterface public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { // we gather all center the user choose. - $centers = array_map(static fn($el) => $el['center'], $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $qb = $this->entityManager->createQueryBuilder(); diff --git a/docs/source/development/FAQ.rst b/docs/source/development/FAQ.rst new file mode 100644 index 000000000..c0b7e37e8 --- /dev/null +++ b/docs/source/development/FAQ.rst @@ -0,0 +1,36 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 +or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU +Free Documentation License". + +.. _faq: + + +Frequently asked questions +#################### + +Continuous integration +*********** + +Pipeline fails, but php-cs-fixer doesn't alert me when running it locally ? +======================================== + +It is possible that you run php-cs-fixer on your local instance of chill and no fixes are made. +Everything seems fine, so you push. However once the pipeline is run in gitlab, you're notified that it failed due to php +cs errors. + +In this case it's likely that you have to update your version of php-cs-fixer. +php-cs-fixer is installed when building the docker image: https://gitea.champs-libres.be/Chill-project/chill-skeleton-basic/src/branch/main/Dockerfile#L50 + +Consequently, to update php-cs-fixer we have to update the image by building it again. + +For this the following commands can be used, + +.. code-block:: php + + docker compose build --pull php + # replace existing containers + docker compose up -d --force-recreate php diff --git a/docs/source/development/database-principles.rst b/docs/source/development/database-principles.rst new file mode 100644 index 000000000..455354934 --- /dev/null +++ b/docs/source/development/database-principles.rst @@ -0,0 +1,84 @@ + +.. database-principles: + +Principes de la base de données +############################### + +Cette page donne une compréhension globale de la base de donnée de Chill, et explique quelques détails d'implémentations qui permettent d'accélérer les traitements à partir de la base de donnée, ou de l'exploiter plus aisément. + +Cette page est rédigée en français. + +.. note:: + + La stabilité du schéma de la base de donnée n'est pas garantie. + + Toutefois, ce dernier évolue relativement peu. Il est rare que des tables ou des colonnes soient supprimées ou renommées. Mais il n'est pas garanti que cela puisse arriver. + +Généralités +=========== + +Une liste commentée de toutes les tables :download:`est disponible au format CSV <./database/table_list.csv`. + +Schéma et conventions de nommage +-------------------------------- + +Au début de l'histoire de Chill, les schémas postgresql n'étaient pas exploités. Les données étaient stockées dans le schéma :code:`public`. + +Par la suite, des nouveaux bundles sont apparus, et les tables ont été classées dans des schémas dédiés. + +A l'heure actuelle: + +- pour les anciens bundle, ceux qui ont déjà des tables dans le schéma public, les nouvelles tables sont ajoutées à ce schéma. Elles sont préfixées par :code:`chill__`; +- pour les bundles plus récents, les tables sont créées dans le schéma dédié + +Données avec de l'historicité +----------------------------- + +Certaines données sont historisées: + +- les référents d'un parcours; +- les statuts d'un parcours; +- la liaison entre les centres et les usagers; +- etc. + +Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable. + +Dans certains cas, la donnée actuelle (référent d'un parcours, par exemple) est également répétée au niveau de la table en elle-même. Par exemple, la table des parcours :code:`chill_person_accompanying_period` comporte une colonne :code:`step` (le statut du parcours) et :code:`user_id` (id du référent) en plus de l'historique. Bien que redondant, cela simplifie les traitements. + +Relations particulières +======================= + +Usagers, ménages, adresses +-------------------------- + +Les usagers ont une adresse au travers des ménages: dans l'interface, l'adresse est inscrite dans le dossier du ménage, et elle est "donnée" aux usagers membres du ménage, **et** qui partagent l'adresse de ce ménage. En effet, il est possible que des usagers "appartiennent" à un ménage sans y être domicilié: c'est le cas, par exemple, des enfants en garde alternée. + +L'historique de l'appartenance des usagers au ménage est conservée, de même que l'historique des adresses pour un même ménage. + +Les tables en jeu sont les suivantes: + +- la table :code:`chill_person_person` liste les usagers; +- la table :code:`chill_person_household_members` liste les appartenances au ménage: il s'agit de la jointure entre les usagers et les ménages: + - les colonnes :code:`startDate` et :code:`endDate` indiquent la date de début et la date de fin de l'appartenance; + - la colonne :code:`shareHousehold` indique si l'utilisateur partage l'adresse du ménage (si oui, sa valeur est :code:`TRUE`) +- la table :code:`chill_person_household` liste les ménages +- la table :code:`chill_person_household_to_addresses` associe les ménages aux adresses; +- la table :code:`chill_main_address` contient les adresses, en indiquant la date de début de validité (:code:`validFrom`) et la fin de validité (:code:`validTo`). + +Pour simplifier la résolution des adresses et des usagers, deux vues ont été mises en œuvre: + +- la vue :code:`view_chill_person_household_address` reprend, pour chaque usager, l'historique des appartenances au ménage découpée par l'historique des adresses d'un ménage. + Autrement dit, une ligne est créée à chaque fois qu'un usager change de ménage, ou qu'un ménage change d'adresse. Il est donc possible de retrouver l'historique complet des adresses pour un usager donné via cette table. +- la vue :code:`view_chill_person_current_address` reprend l'adresse actuelle des usagers. + +Adresses et unités géographiques +-------------------------------- + +Chill propose des statistiques sur la localisation des adresses par rapport à des zones géographiques (:code:`chill_main_geographical_unit`). + +Comme la résolution géographique des adresses est coûteuse en CPU et en temps de traitement, une vue matérialisée a été créée: :code:`view_chill_main_address_geographical_unit`. Elle est rafraichie quotidiennement dans la base de donnée de production. + +Liste des tables et commentaires +================================ + +Une liste commentée de toutes les tables :download:`est disponible au format CSV <./database/table_list.csv`. diff --git a/docs/source/development/database/table_list.csv b/docs/source/development/database/table_list.csv new file mode 100644 index 000000000..fe688318d --- /dev/null +++ b/docs/source/development/database/table_list.csv @@ -0,0 +1,155 @@ +order,table_schema,table_name,commentaire +1,chill_3party,party_category,Catégorie de tiers +2,chill_3party,party_center,Association entre les tiers et les centres (déprécié) +3,chill_3party,party_profession,Profession du tiers (déprécié) +4,chill_3party,third_party,Tiers +5,chill_3party,thirdparty_category,association tiers - catégories +6,chill_asideactivity,asideactivity,Activités annexes +7,chill_asideactivity,asideactivitycategory,Catégories d'activités annexes +8,chill_budget,charge,Charges du budget +9,chill_budget,charge_type,Types de charges +10,chill_budget,resource,Ressources du budget +11,chill_budget,resource_type,Types de ressources +12,chill_calendar,calendar,Rendez-vous +13,chill_calendar,calendar_doc,Document du rendez-vous +14,chill_calendar,calendar_range,Plage de disponibilité +15,chill_calendar,calendar_to_persons,association rendez-vous - usagers +16,chill_calendar,calendar_to_thirdparties,association rendez-vous - tiers +17,chill_calendar,cancel_reason,Motifs d'annulations +18,chill_calendar,invite,Invitation aux rendez-vous +19,chill_doc,accompanyingcourse_document,Documents associés aux parcours +20,chill_doc,document_category,Catégories de documents +21,chill_doc,person_document,Documents associés à l'usagers +22,chill_doc,stored_object,Documents +23,chill_task,recurring_task,Tâches récurrentes (non utilisé) +24,chill_task,single_task,Tâches +25,chill_task,single_task_place_event,Historique des transitions des tâches +26,chill_vendee,adressederelais, +27,chill_vendee,center_polygon +28,chill_vendee,entourage, +29,chill_vendee,geographical_unit +30,chill_vendee,geographical_unit_association +31,chill_vendee,mobilite +32,chill_vendee,niveauetude +33,chill_vendee,security_profile +34,chill_vendee,security_profile_action +35,chill_vendee,security_profile_jobs +36,chill_vendee,situationprofessionelle +37,chill_vendee,statutlogement +38,chill_vendee,tempsdetravail +39,chill_vendee,titredesejour +40,chill_vendee,vendee_person +41,chill_vendee,vendee_person_mineur +42,chill_vendee,vendeeperson_entourage +43,chill_vendee,vendeepersonmineur_adressederelais +44,public,accompanying_periods_scopes,Services associés aux parcours +45,public,activity,Échanges +46,public,activity_activityreason,s +47,public,activity_person, +48,public,activity_storedobject, +49,public,activity_thirdparty, +50,public,activity_user, +51,public,activityreason,Sujets d'échange +52,public,activityreasoncategory,Catégories de sujets +53,public,activitytpresence,Présence aux échanges +54,public,activitytype,Types d'échanges +55,public,activitytypecategory,Catégories de types d'échanges +56,public,centers,"Centres (territoires, agences, etc.)" +57,public,chill_activity_activity_chill_person_socialaction, +58,public,chill_activity_activity_chill_person_socialissue +59,public,chill_docgen_template,Gabarits de documents +60,public,chill_main_address,Adresses +61,public,chill_main_address_legacy,Anciennes adresses (dépréciés) +62,public,chill_main_address_reference,Adresses de référence +63,public,chill_main_civility,Civilités +64,public,chill_main_cronjob_execution,Dernière exécution des tâche cron +65,public,chill_main_geographical_unit,Unités géographiques +66,public,chill_main_geographical_unit_layer,Couches d'unités géographiques +67,public,chill_main_location,Localisations +68,public,chill_main_location_type,Types de localisations +69,public,chill_main_notification,Notifications +70,public,chill_main_notification_addresses_unread +71,public,chill_main_notification_addresses_user +72,public,chill_main_notification_comment, +73,public,chill_main_postal_code,Code postaux +74,public,chill_main_saved_export,Exports enregistrés +75,public,chill_main_user_job,Métiers +76,public,chill_main_workflow_entity,Workflows +77,public,chill_main_workflow_entity_comment +78,public,chill_main_workflow_entity_step,Etapes du workflow +79,public,chill_main_workflow_entity_step_cc_user, +80,public,chill_main_workflow_entity_step_user +81,public,chill_main_workflow_entity_step_user_by_accesskey, +82,public,chill_main_workflow_entity_subscriber_to_final, +83,public,chill_main_workflow_entity_subscriber_to_step +84,public,chill_person_accompanying_period,Parcours d'accompagnement +85,public,chill_person_accompanying_period_closingmotive,Motifs de cloture des parcours +86,public,chill_person_accompanying_period_comment,Commentaires des parcours +87,public,chill_person_accompanying_period_location_history,Historique de la localisatio ndes parcours +88,public,chill_person_accompanying_period_origin,Origine des parcours +89,public,chill_person_accompanying_period_participation,Appartenance des usagers au parcours +90,public,chill_person_accompanying_period_resource,Personnes ressources d'un parcours +91,public,chill_person_accompanying_period_social_issues, +92,public,chill_person_accompanying_period_step_history +93,public,chill_person_accompanying_period_user_history +94,public,chill_person_accompanying_period_work,Actions d'accompagnements +95,public,chill_person_accompanying_period_work_evaluation,Évaluations (dans les actions d'accompagnements) +96,public,chill_person_accompanying_period_work_evaluation_document,Documents des évaluations +97,public,chill_person_accompanying_period_work_goal,Objectifs d'une actions +98,public,chill_person_accompanying_period_work_goal_result,Objectifs et résultats d'une action +99,public,chill_person_accompanying_period_work_person,Usagers associés à une actions +100,public,chill_person_accompanying_period_work_referrer,Référents d'une actions +101,public,chill_person_accompanying_period_work_result,Résultats d'une action +102,public,chill_person_accompanying_period_work_third_party,Tiers traitants d'une action +103,public,chill_person_alt_name,"Noms supplémentaires d'un usager (nom marital, etc.)" +104,public,chill_person_household,Ménages +105,public,chill_person_household_composition, +106,public,chill_person_household_composition_type,Types de composition de ménage +107,public,chill_person_household_members,Membres du ménages +108,public,chill_person_household_position,Positions dans le ménage +109,public,chill_person_household_to_addresses,Association adresses - ménages +110,public,chill_person_marital_status,Etats civils +111,public,chill_person_not_duplicate, +112,public,chill_person_person,Usagers +113,public,chill_person_person_center_history,Historique des centres d'un usagers +114,public,chill_person_persons_to_addresses,Déprécié +115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager +116,public,chill_person_relations,Types de relations de filiation +117,public,chill_person_relationships,Relations de filiations +118,public,chill_person_residential_address,Adresses de résidences +119,public,chill_person_resource,Personnes ressources (pour les personnes) +120,public,chill_person_resource_kind,Type de personnes ressources +121,public,chill_person_social_action,Liste des actions d'accompagnement +122,public,chill_person_social_action_goal,Objectifs associés à une action +123,public,chill_person_social_action_result,Résultats associés à une action +124,public,chill_person_social_issue,Problématiques sociales +125,public,chill_person_social_work_evaluation,Evaluations disponibles +126,public,chill_person_social_work_evaluation_action,Associations entre les évaluations et les actions +127,public,chill_person_social_work_goal,Objectifs disponibles pour une actions +128,public,chill_person_social_work_goal_result,Objectifs et résultats disponible pour une action +129,public,chill_person_social_work_result,Résultats disponibles pour une action +130,public,country,Pays +131,public,custom_field_long_choice_options, +132,public,customfield +133,public,customfieldsdefaultgroup +134,public,customfieldsgroup +135,public,geography_columns,Table liée à postgis +136,public,geometry_columns,Table liée à postgis +137,public,group_centers, +138,public,language,Langues +139,public,messenger_messages,Table système +140,public,migration_versions,Table système +141,public,permission_groups +142,public,permissionsgroup_rolescope +143,public,persons_spoken_languages +144,public,regroupment,Regroupement de centres +145,public,regroupment_center, +146,public,role_scopes, +147,public,scopes,Services +148,public,spatial_ref_sys,Table système (postgis) +149,public,user_groupcenter, +150,public,users,Utilisateurs +151,public,view_chill_person_accompanying_period_info, +152,public,view_chill_person_current_address +153,public,view_chill_person_household_address +154,public,view_chill_person_person_center_history_current diff --git a/docs/source/development/exports.rst b/docs/source/development/exports.rst index 3b01f9e0f..7cb04f32e 100644 --- a/docs/source/development/exports.rst +++ b/docs/source/development/exports.rst @@ -242,3 +242,129 @@ This is an example of the *filter by birthdate*. This filter asks some informati Continue to explain the export framework .. _main bundle: https://git.framasoft.org/Chill-project/Chill-Main + + +With many-to-* relationship, why should we set WHERE clauses in an EXISTS subquery instead of a JOIN ? +`````````````````````````````````````````````````````````````````````````````````````````````````````` + +As we described above, the doctrine builder is converted into a sql query. Let's see how to compute the "number of course +which count at least one activity type with the id 7". For the purpose of this demonstration, we will restrict this on +two accompanying period only: the ones with id 329 and 334. + +Let's see the list of activities associated with those accompanying period: + +.. code-block:: sql + + SELECT id, accompanyingperiod_id, type_id FROM activity WHERE accompanyingperiod_id IN (329, 334) AND type_id = 7 + ORDER BY accompanyingperiod_id; + +We see that we have 6 activities for the accompanying period with id 329, and only one for the 334's one. + +.. csv-table:: + :header: id, accompanyingperiod_id, type_id + + 990,329,7 + 986,329,7 + 987,329,7 + 993,329,7 + 991,329,7 + 992,329,7 + 1000,334,7 + +Let's calculate the average duration for those accompanying periods, and the number of period: + +.. code-block:: sql + + SELECT AVG(age(COALESCE(closingdate, CURRENT_DATE), openingdate)), COUNT(id) from chill_person_accompanying_period WHERE id IN (329, 334); + +The result of this query is: + +.. csv-table:: + :header: AVG, COUNT + + 2 years 2 mons 21 days 12 hours 0 mins 0.0 secs,2 + +Now, we count the number of accompanying period, adding a :code:`JOIN` clause which make a link to the :code:`activity` table, and add a :code:`WHERE` clause to keep +only the accompanying period which contains the given activity type: + +.. code-block:: sql + + SELECT COUNT(chill_person_accompanying_period.id) from chill_person_accompanying_period + JOIN activity ON chill_person_accompanying_period.id = activity.accompanyingperiod_id + WHERE chill_person_accompanying_period.id IN (329, 334) AND activity.type_id = 7; + +What are the results here ? + +.. csv-table:: + :header: COUNT + + 7 + +:code:`7` ! Why this result ? Because the number of lines is duplicated for each activity. Let's see the list of rows which +are taken into account for the computation: + +.. code-block:: sql + + SELECT chill_person_accompanying_period.id, activity.id from chill_person_accompanying_period + JOIN activity ON chill_person_accompanying_period.id = activity.accompanyingperiod_id + WHERE chill_person_accompanying_period.id IN (329, 334) AND activity.type_id = 7; + +.. csv-table:: + :header: accompanyingperiod.id, activity.id + + 329,993 + 334,1000 + 329,987 + 329,990 + 329,991 + 329,992 + 329,986 + +For each activity, a row is created and, as we count the number of non-null :code:`accompanyingperiod.id` columns, we +count one entry for each activity (actually, we count the number of activities). + +So, let's use the :code:`DISTINCT` keyword to count only once the equal ids: + +.. code-block:: + + SELECT COUNT(DISTINCT chill_person_accompanying_period.id) from chill_person_accompanying_period + JOIN activity ON chill_person_accompanying_period.id = activity.accompanyingperiod_id + WHERE chill_person_accompanying_period.id IN (329, 334) AND activity.type_id = 7; + +Now, it works again... + +.. csv-table:: + :header: COUNT + + 2 + +But, for the average duration, this won't work: the duration which are equals (because the :code:`openingdate` is the same and +:code:`closingdate` is still :code:`NULL`, for instance) will be counted only once, which will give unexpected result. + +The solution is to move the condition "having an activity with activity type with id 7" in a :code:`EXISTS` clause: + +.. code-block:: sql + + SELECT COUNT(chill_person_accompanying_period.id) from chill_person_accompanying_period + WHERE chill_person_accompanying_period.id IN (329, 334) AND EXISTS (SELECT 1 FROM activity WHERE type_id = 7 AND accompanyingperiod_id = chill_person_accompanying_period.id); + +The result is correct without :code:`DISTINCT` keyword: + +.. csv-table:: + :header: COUNT + + 2 + +And we can now compute the average duration without fear: + +.. code-block:: sql + + SELECT AVG(age(COALESCE(closingdate, CURRENT_DATE), openingdate)) from chill_person_accompanying_period + WHERE chill_person_accompanying_period.id IN (329, 334) AND EXISTS (SELECT 1 FROM activity WHERE type_id = 7 AND accompanyingperiod_id = chill_person_accompanying_period.id); + +Give the result: + +.. csv-table:: + :header: AVG + + 2 years 2 mons 21 days 12 hours 0 mins 0.0 secs diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst index fd9ae43ba..d48f92890 100644 --- a/docs/source/development/index.rst +++ b/docs/source/development/index.rst @@ -9,7 +9,7 @@ Development ########### -As Chill rely on the `symfony `_ framework, reading the framework's documentation should answer most of your questions. We are explaining here some tips to work with Chill, and things we provide to encounter our needs. +As Chill relies on the `symfony `_ framework, reading the framework's documentation should answer most of your questions. We are explaining here some tips to work with Chill, and help with things we've encountered. .. toctree:: :maxdepth: 2 @@ -36,6 +36,8 @@ As Chill rely on the `symfony `_ framework, reading the fram Assets Cron Jobs Info about entities + Info about database (in French) + Developer FAQ Layout and UI ************** diff --git a/docs/source/development/pagination/example.php b/docs/source/development/pagination/example.php index 0d79395a7..964da8764 100644 --- a/docs/source/development/pagination/example.php +++ b/docs/source/development/pagination/example.php @@ -13,7 +13,7 @@ namespace Chill\MyBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; -class example extends Controller +class example extends \Symfony\Bundle\FrameworkBundle\Controller\AbstractController { public function yourAction() { diff --git a/docs/source/development/useful-snippets/controller-secured-for-person.php b/docs/source/development/useful-snippets/controller-secured-for-person.php index 7c8ec1931..da2078a7a 100644 --- a/docs/source/development/useful-snippets/controller-secured-for-person.php +++ b/docs/source/development/useful-snippets/controller-secured-for-person.php @@ -16,7 +16,7 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Security\Core\Role\Role; -class ConsultationController extends Controller +class ConsultationController extends \Symfony\Bundle\FrameworkBundle\Controller\AbstractController { /** * @param int $id personId @@ -43,7 +43,7 @@ class ConsultationController extends Controller $circles = $authorizationHelper->getReachableCircles( $this->getUser(), - new Role(ConsultationVoter::SEE), + ConsultationVoter::SEE, $person->getCenter() ); diff --git a/docs/source/development/user-interface/widgets/ChillMainConfiguration.php b/docs/source/development/user-interface/widgets/ChillMainConfiguration.php index 9eca0ae76..c3a90dfee 100644 --- a/docs/source/development/user-interface/widgets/ChillMainConfiguration.php +++ b/docs/source/development/user-interface/widgets/ChillMainConfiguration.php @@ -23,25 +23,18 @@ class ChillMainConfiguration implements ConfigurationInterface { use AddWidgetConfigurationTrait; - /** - * @var ContainerBuilder - */ - private $containerBuilder; - public function __construct( array $widgetFactories, - ContainerBuilder $containerBuilder + private readonly ContainerBuilder $containerBuilder ) { // we register here widget factories (see below) $this->setWidgetFactories($widgetFactories); - // we will need the container builder later... - $this->containerBuilder = $containerBuilder; } public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('chill_main'); + $treeBuilder = new TreeBuilder('chill_main'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() @@ -56,7 +49,7 @@ class ChillMainConfiguration implements ConfigurationInterface ->end() // end of widgets ->end() // end of root/children ->end() // end of root -; + ; return $treeBuilder; } diff --git a/docs/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php b/docs/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php index 0c8ed3782..0c3f2c182 100644 --- a/docs/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php +++ b/docs/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php @@ -87,7 +87,7 @@ class ChillPersonAddAPersonWidget implements WidgetInterface // show only the person from the authorized centers $and = $qb->expr()->andX(); $centers = $this->authorizationHelper - ->getReachableCenters($this->getUser(), new Role(PersonVoter::SEE)); + ->getReachableCenters($this->getUser(), PersonVoter::SEE); $and->add($qb->expr()->in('person.center', ':centers')); $qb->setParameter('centers', $centers); diff --git a/docs/source/installation/index.rst b/docs/source/installation/index.rst index 910122196..67a0b6ec6 100644 --- a/docs/source/installation/index.rst +++ b/docs/source/installation/index.rst @@ -48,8 +48,8 @@ Clone or download the chill-skeleton project and `cd` into the main directory. .. code-block:: bash - git clone https://gitlab.com/Chill-Projet/chill-skeleton-basic.git - cd chill-app + git clone https://gitea.champs-libres.be/Chill-project/chill-skeleton-basic.git + cd chill-skeleton-basic As a developer, the code will stay on your computer and will be executed in docker container. To avoid permission problem, the code should be run with the same uid/gid from your current user. This is why we get your current user id with the command ``id -u`` in each following scripts. diff --git a/docs/source/installation/load-addresses.rst b/docs/source/installation/load-addresses.rst index 779032fd0..85c29d618 100644 --- a/docs/source/installation/load-addresses.rst +++ b/docs/source/installation/load-addresses.rst @@ -8,6 +8,16 @@ Chill can store a list of geolocated address references, which are used to sugge Those addresses may be load from a dedicated source. +Countries +========= + +In order to load addresses into the chill application we first have to make sure that a list of countries is present. +To import the countries run the following command. + +.. code-block:: bash + + bin/console chill:main:countries:populate + In France ========= diff --git a/docs/source/installation/msgraph-configure.rst b/docs/source/installation/msgraph-configure.rst index 2a0a17882..5655e9ff2 100644 --- a/docs/source/installation/msgraph-configure.rst +++ b/docs/source/installation/msgraph-configure.rst @@ -1,13 +1,15 @@ -Configure Chill for calendar sync and SSO with Microsoft Graph (Outlook) -======================================================================== +Configure Chill for calendar and absence synchronisation and SSO with Microsoft Graph (Outlook) +=============================================================================================== Chill offers the possibility to: * authenticate users using Microsoft Graph, with relatively small adaptations; * synchronize calendar in both ways (`see the user manual for a large description of the feature `_). -Both can be configured separately (synchronising calendars without SSO, or SSO without calendar). When calendar sync is configured without SSL, the user's email address is the key to associate Chill's users with Microsoft's ones. +Both can be configured separately (synchronising calendars without SSO, or SSO without calendar). + +Please note that the user's email address is the key to associate Chill's users with Microsoft's ones. Configure SSO ------------- @@ -46,7 +48,7 @@ Do not forget to provider user's accesses to your app, using the "Utilisateurs e You must know have gathered all the required variables for SSO: -.. code-block:: +.. code-block:: SAML_BASE_URL=https://test.chill.be # must be SAML_ENTITY_ID=https://test.chill.be # must match the one entered @@ -186,20 +188,27 @@ Configure chill app -Configure sync --------------- +Configure sync and calendar access +---------------------------------- -The sync processe might be configured in the same app, or into a different app. +The purpose of this configuration is the following: -The synchronization processes use Oauth2.0 for authentication and authorization. +- let user read their calendar and shared calendar within Chill (with the same permissions as the one configured in Outlook / Azure); +- allow chill instance to write appointment ("Rendez-vous") into their calendar, and invite other users to their appointment; +- allow chill instance to be notified if an appoint is added or removed by the user within another interface than Chill: if the appointment match another one created in the Chill interface, the date and time are updated in Chill; +- allow chill instance to read the absence of the user and, if set, mark the user as absent in Chill; + +The sync processe might be configured in the same app, or into a different app on the Azure side. + +The synchronization processes use Oauth 2.0 / OpenID Connect for authentication and authorization. .. note:: Two flows are in use: - * we authenticate "on behalf of a user", to allow users to see their own calendar or other user's calendar into the web interface. + * we authenticate "on behalf of a user", to allow users to see their own calendar or other user's calendar into the web interface. - Typically, when the page is loaded, Chill first check that an authorization token exists. If not, the user is redirected to Microsoft Azure for authentification and a new token is grabbed (most of the times, this is transparent for users). + Typically, when the page is loaded, Chill first check that an authorization token exists. If not, the user is redirected to Microsoft Azure for authentification and a new token is grabbed (most of the times, this is transparent for users). * Chill also acts "as a machine", to synchronize calendars with a daemon background. @@ -229,8 +238,9 @@ Some explanation: The sync daemon must have write access: * the daemon must be allowed to read all users and their profile, to establish a link between them and the Chill's users: (:code:`Users.Read.All`); -* it must also be allowed to read and write into the calendars (:code:`Calendars.ReadWrite.All`) -* for sending invitation to other users, the permission (:code:`Mail.Send`) must be granted. +* it must also be allowed to read and write into the calendars (:code:`Calendars.ReadWrite.All`); +* for sending invitation to other users, the permission (:code:`Mail.Send`) must be granted; +* and, for reading the absence status of the user and sync it with chill, it must be able to read the mailboxSettings (:code:`MailboxSettings.Read`). At this step, you might choose to accept those permissions for all users, or let them do it by yourself. @@ -301,7 +311,7 @@ The calendar synchronization is processed using symfony messenger. It seems to b The association between chill's users and Microsoft's users is done by this cli command: -.. code-block:: +.. code-block:: bin/console chill:calendar:msgraph-user-map-subscribe diff --git a/docs/source/installation/oauth_api_autorisees.png b/docs/source/installation/oauth_api_autorisees.png index 41222f971..ef4136b10 100644 Binary files a/docs/source/installation/oauth_api_autorisees.png and b/docs/source/installation/oauth_api_autorisees.png differ diff --git a/exports_alias_conventions.csv b/exports_alias_conventions.csv deleted file mode 100644 index ab32cda8e..000000000 --- a/exports_alias_conventions.csv +++ /dev/null @@ -1,63 +0,0 @@ -Entity,Join,Attribute,Alias -AccompanyingPeriod::class,,,acp -,AccompanyingPeriodWork::class,acp.works,acpw -,AccompanyingPeriodParticipation::class,acp.participations,acppart -,Location::class,acp.administrativeLocation,acploc -,ClosingMotive::class,acp.closingMotive,acpmotive -,UserJob::class,acp.job,acpjob -,Origin::class,acp.origin,acporigin -,Scope::class,acp.scopes,acpscope -,SocialIssue::class,acp.socialIssues,acpsocialissue -,User::class,acp.user,acpuser -AccompanyingPeriodWork::class,,,acpw -,AccompanyingPeriodWorkEvaluation::class,acpw.accompanyingPeriodWorkEvaluations,workeval -,User::class,acpw.referrers,acpwuser -,SocialAction::class,acpw.socialAction,acpwsocialaction -,Goal::class,acpw.goals,goal -,Result::class,acpw.results,result -AccompanyingPeriodParticipation::class,,,acppart -,Person::class,acppart.person,partperson -AccompanyingPeriodWorkEvaluation::class,,,workeval -,Evaluation::class,workeval.evaluation,eval -Goal::class,,,goal -,Result::class,goal.results,goalresult -Person::class,,,person -,Center::class,person.center,center -,HouseholdMember::class,partperson.householdParticipations,householdmember -,MaritalStatus::class,person.maritalStatus,personmarital -,VendeePerson::class,,vp -,VendeePersonMineur::class,,vpm -ResidentialAddress::class,,,resaddr -,ThirdParty::class,resaddr.hostThirdParty,tparty -ThirdParty::class,,,tparty -,ThirdPartyCategory::class,tparty.categories,tpartycat -HouseholdMember::class,,,householdmember -,Household::class,householdmember.household,household -,Person::class,householdmember.person,memberperson -,,memberperson.center,membercenter -Household::class,,,household -,HouseholdComposition::class,household.compositions,composition -Activity::class,,,activity -,Person::class,activity.person,actperson -,AccompanyingPeriod::class,activity.accompanyingPeriod,acp -,Person::class,activity_person_having_activity.person,person_person_having_activity -,ActivityReason::class,activity_person_having_activity.reasons,reasons_person_having_activity -,ActivityType::class,activity.activityType,acttype -,Location::class,activity.location,actloc -,SocialAction::class,activity.socialActions,actsocialaction -,SocialIssue::class,activity.socialIssues,actsocialssue -,ThirdParty::class,activity.thirdParties,acttparty -,User::class,activity.user,actuser -,User::class,activity.users,actusers -,ActivityReason::class,activity.reasons,actreasons -,Center::class,actperson.center,actcenter -ActivityReason::class,,,actreasons -,ActivityReasonCategory::class,actreason.category,actreasoncat -Calendar::class,,,cal -,CancelReason::class,cal.cancelReason,calcancel -,Location::class,cal.location,calloc -,User::class,cal.user,caluser -VendeePerson::class,,,vp -,SituationProfessionelle::class,vp.situationProfessionelle,vpprof -,StatutLogement::class,vp.statutLogement,vplog -,TempsDeTravail::class,vp.tempsDeTravail,vptt diff --git a/exports_alias_conventions.md b/exports_alias_conventions.md index 64df91030..d4d034314 100644 --- a/exports_alias_conventions.md +++ b/exports_alias_conventions.md @@ -5,73 +5,74 @@ Add condition with distinct alias on each export join clauses (Indicators + Filt These are alias conventions : -| Entity | Join | Attribute | Alias | -|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:---------------------------------------| -| AccompanyingPeriod::class | | | acp | -| | AccompanyingPeriodWork::class | acp.works | acpw | -| | AccompanyingPeriodParticipation::class | acp.participations | acppart | -| | Location::class | acp.administrativeLocation | acploc | -| | ClosingMotive::class | acp.closingMotive | acpmotive | -| | UserJob::class | acp.job | acpjob | -| | Origin::class | acp.origin | acporigin | -| | Scope::class | acp.scopes | acpscope | -| | SocialIssue::class | acp.socialIssues | acpsocialissue | -| | User::class | acp.user | acpuser | -| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories | -| | AccompanyingPeriodInfo::class | not existing (using custom WITH clause) | acpinfo | -| AccompanyingPeriodWork::class | | | acpw | -| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval | -| | User::class | acpw.referrers | acpwuser | -| | SocialAction::class | acpw.socialAction | acpwsocialaction | -| | Goal::class | acpw.goals | goal | -| | Result::class | acpw.results | result | -| AccompanyingPeriodParticipation::class | | | acppart | -| | Person::class | acppart.person | partperson | -| AccompanyingPeriodWorkEvaluation::class | | | workeval | -| | Evaluation::class | workeval.evaluation | eval | -| AccompanyingPeriodInfo::class | | | acpinfo | -| | User::class | acpinfo.user | acpinfo_user | -| Goal::class | | | goal | -| | Result::class | goal.results | goalresult | -| Person::class | | | person | -| | Center::class | person.center | center | -| | HouseholdMember::class | partperson.householdParticipations | householdmember | -| | MaritalStatus::class | person.maritalStatus | personmarital | -| | VendeePerson::class | | vp | -| | VendeePersonMineur::class | | vpm | -| | CurrentPersonAddress::class | person.currentPersonAddress | currentPersonAddress (on a given date) | -| ResidentialAddress::class | | | resaddr | -| | ThirdParty::class | resaddr.hostThirdParty | tparty | -| ThirdParty::class | | | tparty | -| | ThirdPartyCategory::class | tparty.categories | tpartycat | -| HouseholdMember::class | | | householdmember | -| | Household::class | householdmember.household | household | -| | Person::class | householdmember.person | memberperson | -| | | memberperson.center | membercenter | -| Household::class | | | household | -| | HouseholdComposition::class | household.compositions | composition | -| Activity::class | | | activity | -| | Person::class | activity.person | actperson | -| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp | -| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity | -| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity | -| | ActivityType::class | activity.activityType | acttype | -| | Location::class | activity.location | actloc | -| | SocialAction::class | activity.socialActions | actsocialaction | -| | SocialIssue::class | activity.socialIssues | actsocialssue | -| | ThirdParty::class | activity.thirdParties | acttparty | -| | User::class | activity.user | actuser | -| | User::class | activity.users | actusers | -| | ActivityReason::class | activity.reasons | actreasons | -| | Center::class | actperson.center | actcenter | -| | Person::class | activity.createdBy | actcreator | -| ActivityReason::class | | | actreasons | -| | ActivityReasonCategory::class | actreason.category | actreasoncat | -| Calendar::class | | | cal | -| | CancelReason::class | cal.cancelReason | calcancel | -| | Location::class | cal.location | calloc | -| | User::class | cal.user | caluser | -| VendeePerson::class | | | vp | -| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof | -| | StatutLogement::class | vp.statutLogement | vplog | -| | TempsDeTravail::class | vp.tempsDeTravail | vptt | +| Entity | Join | Attribute | Alias | +|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:-------------------------------------------| +| AccompanyingPeriodStepHistory::class | | | acpstephistory (contexte ACP_STEP_HISTORY) | +| | AccompanyingPeriod::class | acpstephistory.period | acp | +| AccompanyingPeriod::class | | | acp | +| | AccompanyingPeriodWork::class | acp.works | acpw | +| | AccompanyingPeriodParticipation::class | acp.participations | acppart | +| | Location::class | acp.administrativeLocation | acploc | +| | ClosingMotive::class | acp.closingMotive | acpmotive | +| | UserJob::class | acp.job | acpjob | +| | Origin::class | acp.origin | acporigin | +| | Scope::class | acp.scopes | acpscope | +| | SocialIssue::class | acp.socialIssues | acpsocialissue | +| | User::class | acp.user | acpuser | +| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories | +| | AccompanyingPeriodInfo::class | not existing (using custom WITH clause) | acpinfo | +| AccompanyingPeriodWork::class | | | acpw | +| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval | +| | SocialAction::class | acpw.socialAction | acpwsocialaction | +| | Goal::class | acpw.goals | goal | +| | Result::class | acpw.results | result | +| AccompanyingPeriodParticipation::class | | | acppart | +| | Person::class | acppart.person | partperson | +| AccompanyingPeriodWorkEvaluation::class | | | workeval | +| | Evaluation::class | workeval.evaluation | eval | +| AccompanyingPeriodInfo::class | | | acpinfo | +| | User::class | acpinfo.user | acpinfo_user | +| Goal::class | | | goal | +| | Result::class | goal.results | goalresult | +| Person::class | | | person | +| | Center::class | person.center | center | +| | HouseholdMember::class | partperson.householdParticipations | householdmember | +| | MaritalStatus::class | person.maritalStatus | personmarital | +| | VendeePerson::class | | vp | +| | VendeePersonMineur::class | | vpm | +| | CurrentPersonAddress::class | person.currentPersonAddress | currentPersonAddress (on a given date) | +| ResidentialAddress::class | | | resaddr | +| | ThirdParty::class | resaddr.hostThirdParty | tparty | +| ThirdParty::class | | | tparty | +| | ThirdPartyCategory::class | tparty.categories | tpartycat | +| HouseholdMember::class | | | householdmember | +| | Household::class | householdmember.household | household | +| | Person::class | householdmember.person | memberperson | +| | | memberperson.center | membercenter | +| Household::class | | | household | +| | HouseholdComposition::class | household.compositions | composition | +| Activity::class | | | activity | +| | Person::class | activity.person | actperson | +| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp | +| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity | +| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity | +| | ActivityType::class | activity.activityType | acttype | +| | Location::class | activity.location | actloc | +| | SocialAction::class | activity.socialActions | actsocialaction | +| | SocialIssue::class | activity.socialIssues | actsocialssue | +| | ThirdParty::class | activity.thirdParties | acttparty | +| | User::class | activity.user | actuser | +| | User::class | activity.users | actusers | +| | ActivityReason::class | activity.reasons | actreasons | +| | Center::class | actperson.center | actcenter | +| | Person::class | activity.createdBy | actcreator | +| ActivityReason::class | | | actreasons | +| | ActivityReasonCategory::class | actreason.category | actreasoncat | +| Calendar::class | | | cal | +| | CancelReason::class | cal.cancelReason | calcancel | +| | Location::class | cal.location | calloc | +| | User::class | cal.user | caluser | +| VendeePerson::class | | | vp | +| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof | +| | StatutLogement::class | vp.statutLogement | vplog | +| | TempsDeTravail::class | vp.tempsDeTravail | vptt | diff --git a/package.json b/package.json index 590976cfa..6b494a68d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@symfony/webpack-encore": "^4.1.0", "@tsconfig/node14": "^1.0.1", "bindings": "^1.5.0", - "bootstrap": "^5.0.1", + "bootstrap": "5.2.3", "chokidar": "^3.5.1", "fork-awesome": "^1.1.7", "jquery": "^3.6.0", diff --git a/phpstan-baseline-2024-05.neon b/phpstan-baseline-2024-05.neon new file mode 100644 index 000000000..728ec4edc --- /dev/null +++ b/phpstan-baseline-2024-05.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$records of method League\\\\Csv\\\\Writer\\:\\:insertAll\\(\\) expects iterable\\\\>, iterable\\\\> given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/UserExportController.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 62dbe0468..4e6745469 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -31,4 +31,5 @@ includes: - phpstan-baseline-level-3.neon - phpstan-baseline-level-4.neon - phpstan-baseline-level-5.neon + - phpstan-baseline-2024-05.neon diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8f157fcc5..aa519e376 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,26 +2,51 @@ - + + + + + src/Bundle/ChillAsideActivityBundle/src/Tests/ + + + src/Bundle/ChillBudgetBundle/Tests/ + + + src/Bundle/ChillCalendarBundle/Tests/ + + + + src/Bundle/ChillDocGeneratorBundle/tests/ + + + src/Bundle/ChillDocStoreBundle/Tests/ + + src/Bundle/ChillMainBundle/Tests/ src/Bundle/ChillPersonBundle/Tests/ - - src/Bundle/ChillPersonBundle/Tests/Export/* src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingPeriodControllerTest.php @@ -31,14 +56,18 @@ src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php - - src/Bundle/ChillAsideActivityBundle/src/Tests/ + + + + src/Bundle/ChillThirdPartyBundle/Tests src/Bundle/ChillWopiBundle/tests/ diff --git a/rector.php b/rector.php index c2e752c32..8aa62bdd0 100644 --- a/rector.php +++ b/rector.php @@ -2,6 +2,13 @@ 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. + */ + use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; @@ -12,6 +19,9 @@ return static function (RectorConfig $rectorConfig): void { __DIR__ . '/src', ]); + $rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/testsApp_KernelDevDebugContainer.xml'); + $rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php'); + //$rectorConfig->cacheClass(\Rector\Caching\ValueObject\Storage\FileCacheStorage::class); //$rectorConfig->cacheDirectory(__DIR__ . '/.cache/rector'); @@ -21,43 +31,22 @@ return static function (RectorConfig $rectorConfig): void { //define sets of rules $rectorConfig->sets([ - LevelSetList::UP_TO_PHP_74 + LevelSetList::UP_TO_PHP_82, + \Rector\Symfony\Set\SymfonyLevelSetList::UP_TO_SYMFONY_44, + \Rector\Doctrine\Set\DoctrineSetList::DOCTRINE_CODE_QUALITY, + \Rector\PHPUnit\Set\PHPUnitLevelSetList::UP_TO_PHPUNIT_90, ]); + // some routes are added twice if it remains activated + // $rectorConfig->rule(\Rector\Symfony\Configs\Rector\ClassMethod\AddRouteAnnotationRector::class); + // chill rules - $rectorConfig->rule(\Chill\Utils\Rector\Rector\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector::class); + //$rectorConfig->rule(\Chill\Utils\Rector\Rector\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector::class); // skip some path... $rectorConfig->skip([ - // make rector stuck for some files - \Rector\Php56\Rector\FunctionLike\AddDefaultValueForUndefinedVariableRector::class, - - // we need to discuss this: are we going to have FALSE in tests instead of an error ? - \Rector\Php71\Rector\FuncCall\CountOnNullRector::class, - - // must merge MR500 and review a typing of "ArrayCollection" in entities - \Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::class, - - // remove all PHP80 rules, in order to activate them one by one - \Rector\Php80\Rector\ClassMethod\AddParamBasedOnParentClassMethodRector::class, - \Rector\Php80\Rector\Class_\AnnotationToAttributeRector::class, - \Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector::class, - \Rector\Php80\Rector\FuncCall\ClassOnObjectRector::class, - \Rector\Php80\Rector\ClassConstFetch\ClassOnThisVariableObjectRector::class, - \Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector::class, - \Rector\Php80\Rector\Class_\DoctrineAnnotationClassToAttributeRector::class, - \Rector\Php80\Rector\ClassMethod\FinalPrivateToPrivateVisibilityRector::class, - \Rector\Php80\Rector\Ternary\GetDebugTypeRector::class, - \Rector\Php80\Rector\FunctionLike\MixedTypeRector::class, - \Rector\Php80\Rector\Property\NestedAnnotationToAttributeRector::class, - \Rector\Php80\Rector\FuncCall\Php8ResourceReturnToObjectRector::class, - \Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector::class, - \Rector\Php80\Rector\ClassMethod\SetStateToStaticRector::class, - \Rector\Php80\Rector\NotIdentical\StrContainsRector::class, - \Rector\Php80\Rector\Identical\StrEndsWithRector::class, - \Rector\Php80\Rector\Identical\StrStartsWithRector::class, - \Rector\Php80\Rector\Class_\StringableForToStringRector::class, - \Rector\Php80\Rector\FuncCall\TokenGetAllToObjectRector::class, - \Rector\Php80\Rector\FunctionLike\UnionTypesRector::class + // we must adapt service definition + \Rector\Symfony\Symfony28\Rector\MethodCall\GetToConstructorInjectionRector::class, + \Rector\Symfony\Symfony34\Rector\Closure\ContainerGetNameToTypeInTestsRector::class, ]); }; diff --git a/src/Bundle/ChillActivityBundle/CHANGELOG.md b/src/Bundle/ChillActivityBundle/CHANGELOG.md index 6261aa1db..4cda36f0c 100644 --- a/src/Bundle/ChillActivityBundle/CHANGELOG.md +++ b/src/Bundle/ChillActivityBundle/CHANGELOG.md @@ -28,3 +28,4 @@ Version 1.5.5 - [activity] replace dropdown for selecting reasons and use chillEntity for reason rendering - fix bug: error when trying to edit activity of which the type has been deactivated + diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php index 444c663fc..153b87f44 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php @@ -20,20 +20,22 @@ use Chill\ActivityBundle\Repository\ActivityTypeCategoryRepository; use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; +use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Repository\LocationRepository; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; use Chill\PersonBundle\Repository\PersonRepository; use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; -use DateTime; use Doctrine\ORM\EntityManagerInterface; -use InvalidArgumentException; use Psr\Log\LoggerInterface; -use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Core\Type\HiddenType; @@ -43,80 +45,37 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Contracts\Translation\TranslatorInterface; -use function array_key_exists; final class ActivityController extends AbstractController { - private AccompanyingPeriodRepository $accompanyingPeriodRepository; - - private ActivityACLAwareRepositoryInterface $activityACLAwareRepository; - - private ActivityRepository $activityRepository; - - private ActivityTypeCategoryRepository $activityTypeCategoryRepository; - - private ActivityTypeRepositoryInterface $activityTypeRepository; - - private CenterResolverManagerInterface $centerResolver; - - private EntityManagerInterface $entityManager; - - private EventDispatcherInterface $eventDispatcher; - - private LocationRepository $locationRepository; - - private LoggerInterface $logger; - - private PersonRepository $personRepository; - - private SerializerInterface $serializer; - - private ThirdPartyRepository $thirdPartyRepository; - - private TranslatorInterface $translator; - - private UserRepositoryInterface $userRepository; - public function __construct( - ActivityACLAwareRepositoryInterface $activityACLAwareRepository, - ActivityTypeRepositoryInterface $activityTypeRepository, - ActivityTypeCategoryRepository $activityTypeCategoryRepository, - PersonRepository $personRepository, - ThirdPartyRepository $thirdPartyRepository, - LocationRepository $locationRepository, - ActivityRepository $activityRepository, - AccompanyingPeriodRepository $accompanyingPeriodRepository, - EntityManagerInterface $entityManager, - EventDispatcherInterface $eventDispatcher, - LoggerInterface $logger, - SerializerInterface $serializer, - UserRepositoryInterface $userRepository, - CenterResolverManagerInterface $centerResolver, - TranslatorInterface $translator + private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository, + private readonly ActivityTypeRepositoryInterface $activityTypeRepository, + private readonly ActivityTypeCategoryRepository $activityTypeCategoryRepository, + private readonly PersonRepository $personRepository, + private readonly ThirdPartyRepository $thirdPartyRepository, + private readonly LocationRepository $locationRepository, + private readonly ActivityRepository $activityRepository, + private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, + private readonly EntityManagerInterface $entityManager, + private readonly EventDispatcherInterface $eventDispatcher, + private readonly LoggerInterface $logger, + private readonly SerializerInterface $serializer, + private readonly UserRepositoryInterface $userRepository, + private readonly CenterResolverManagerInterface $centerResolver, + private readonly TranslatorInterface $translator, + private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory, + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly PaginatorFactory $paginatorFactory, ) { - $this->activityACLAwareRepository = $activityACLAwareRepository; - $this->activityTypeRepository = $activityTypeRepository; - $this->activityTypeCategoryRepository = $activityTypeCategoryRepository; - $this->personRepository = $personRepository; - $this->thirdPartyRepository = $thirdPartyRepository; - $this->locationRepository = $locationRepository; - $this->activityRepository = $activityRepository; - $this->accompanyingPeriodRepository = $accompanyingPeriodRepository; - $this->entityManager = $entityManager; - $this->eventDispatcher = $eventDispatcher; - $this->logger = $logger; - $this->serializer = $serializer; - $this->userRepository = $userRepository; - $this->centerResolver = $centerResolver; - $this->translator = $translator; } /** * Deletes a Activity entity. * - * @param mixed $id + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/{id}/delete", name="chill_activity_activity_delete", methods={"GET", "POST", "DELETE"}) */ - public function deleteAction(Request $request, $id) + public function deleteAction(Request $request, mixed $id) { $view = null; @@ -129,10 +88,10 @@ final class ActivityController extends AbstractController } if ($activity->getAccompanyingPeriod() instanceof AccompanyingPeriod) { - $view = 'ChillActivityBundle:Activity:confirm_deleteAccompanyingCourse.html.twig'; + $view = '@ChillActivity/Activity/confirm_deleteAccompanyingCourse.html.twig'; $accompanyingPeriod = $activity->getAccompanyingPeriod(); } else { - $view = 'ChillActivityBundle:Activity:confirm_deletePerson.html.twig'; + $view = '@ChillActivity/Activity/confirm_deletePerson.html.twig'; } // TODO @@ -140,7 +99,7 @@ final class ActivityController extends AbstractController $form = $this->createDeleteForm($activity->getId(), $person, $accompanyingPeriod); - if ($request->getMethod() === Request::METHOD_DELETE) { + if (Request::METHOD_DELETE === $request->getMethod()) { $form->handleRequest($request); if ($form->isValid()) { @@ -173,10 +132,6 @@ final class ActivityController extends AbstractController } } - if (null === $view) { - throw $this->createNotFoundException('Template not found'); - } - return $this->render($view, [ 'activity' => $activity, 'delete_form' => $form->createView(), @@ -187,6 +142,8 @@ final class ActivityController extends AbstractController /** * Displays a form to edit an existing Activity entity. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/{id}/edit", name="chill_activity_activity_edit", methods={"GET", "POST", "PUT"}) */ public function editAction(int $id, Request $request): Response { @@ -202,10 +159,10 @@ final class ActivityController extends AbstractController $person = $entity->getPerson(); if ($entity->getAccompanyingPeriod() instanceof AccompanyingPeriod) { - $view = 'ChillActivityBundle:Activity:editAccompanyingCourse.html.twig'; + $view = '@ChillActivity/Activity/editAccompanyingCourse.html.twig'; $accompanyingPeriod = $entity->getAccompanyingPeriod(); } else { - $view = 'ChillActivityBundle:Activity:editPerson.html.twig'; + $view = '@ChillActivity/Activity/editPerson.html.twig'; } // TODO // $this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity); @@ -266,10 +223,6 @@ final class ActivityController extends AbstractController $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); */ - if (null === $view) { - throw $this->createNotFoundException('Template not found'); - } - $activity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']); return $this->render($view, [ @@ -284,34 +237,64 @@ final class ActivityController extends AbstractController /** * Lists all Activity entities. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/", name="chill_activity_activity_list") */ public function listAction(Request $request): Response { $view = null; $activities = []; - // TODO: add pagination [$person, $accompanyingPeriod] = $this->getEntity($request); + $filter = $this->buildFilterOrder($person ?? $accompanyingPeriod); + + $filterArgs = [ + 'my_activities' => $filter->getSingleCheckboxData('my_activities'), + 'types' => $filter->hasEntityChoice('activity_types') ? $filter->getEntityChoiceData('activity_types') : [], + 'jobs' => $filter->hasEntityChoice('jobs') ? $filter->getEntityChoiceData('jobs') : [], + 'before' => $filter->getDateRangeData('activity_date')['to'], + 'after' => $filter->getDateRangeData('activity_date')['from'], + ]; if ($person instanceof Person) { $this->denyAccessUnlessGranted(ActivityVoter::SEE, $person); + $count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE, $filterArgs); + $paginator = $this->paginatorFactory->create($count); $activities = $this->activityACLAwareRepository - ->findByPerson($person, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']); + ->findByPerson( + $person, + ActivityVoter::SEE, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage(), + ['date' => 'DESC', 'id' => 'DESC'], + $filterArgs + ); $event = new PrivacyEvent($person, [ 'element_class' => Activity::class, 'action' => 'list', ]); - $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); + $this->eventDispatcher->dispatch($event, PrivacyEvent::PERSON_PRIVACY_EVENT); - $view = 'ChillActivityBundle:Activity:listPerson.html.twig'; + $view = '@ChillActivity/Activity/listPerson.html.twig'; } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) { $this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod); + $count = $this->activityACLAwareRepository->countByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, $filterArgs); + $paginator = $this->paginatorFactory->create($count); $activities = $this->activityACLAwareRepository - ->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']); + ->findByAccompanyingPeriod( + $accompanyingPeriod, + ActivityVoter::SEE, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage(), + ['date' => 'DESC', 'id' => 'DESC'], + $filterArgs + ); - $view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig'; + $view = '@ChillActivity/Activity/listAccompanyingCourse.html.twig'; + } else { + throw new \LogicException('Unsupported'); } return $this->render( @@ -320,10 +303,49 @@ final class ActivityController extends AbstractController 'activities' => $activities, 'person' => $person, 'accompanyingCourse' => $accompanyingPeriod, + 'filter' => $filter, + 'paginator' => $paginator, ] ); } + private function buildFilterOrder(AccompanyingPeriod|Person $associated): FilterOrderHelper + { + $filterBuilder = $this->filterOrderHelperFactory->create(self::class); + $types = $this->activityACLAwareRepository->findActivityTypeByAssociated($associated); + $jobs = $this->activityACLAwareRepository->findUserJobByAssociated($associated); + + $filterBuilder + ->addDateRange('activity_date', 'activity.date') + ->addSingleCheckbox('my_activities', 'activity_filter.My activities'); + + if (1 < count($types)) { + $filterBuilder + ->addEntityChoice('activity_types', 'activity_filter.Types', \Chill\ActivityBundle\Entity\ActivityType::class, $types, [ + 'choice_label' => function (\Chill\ActivityBundle\Entity\ActivityType $activityType) { + $text = match ($activityType->hasCategory()) { + true => $this->translatableStringHelper->localize($activityType->getCategory()->getName()).' > ', + false => '', + }; + + return $text.$this->translatableStringHelper->localize($activityType->getName()); + }, + ]); + } + + if (1 < count($jobs)) { + $filterBuilder + ->addEntityChoice('jobs', 'activity_filter.Jobs', UserJob::class, $jobs, [ + 'choice_label' => fn (UserJob $u) => $this->translatableStringHelper->localize($u->getLabel()), + ]); + } + + return $filterBuilder->build(); + } + + /** + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/new", name="chill_activity_activity_new", methods={"POST", "GET"}) + */ public function newAction(Request $request): Response { $view = null; @@ -331,16 +353,16 @@ final class ActivityController extends AbstractController [$person, $accompanyingPeriod] = $this->getEntity($request); if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $view = 'ChillActivityBundle:Activity:newAccompanyingCourse.html.twig'; + $view = '@ChillActivity/Activity/newAccompanyingCourse.html.twig'; } elseif ($person instanceof Person) { - $view = 'ChillActivityBundle:Activity:newPerson.html.twig'; + $view = '@ChillActivity/Activity/newPerson.html.twig'; } $activityType_id = $request->get('activityType_id', 0); $activityType = $this->activityTypeRepository->find($activityType_id); if (isset($activityType) && !$activityType->isActive()) { - throw new InvalidArgumentException('Activity type must be active'); + throw new \InvalidArgumentException('Activity type must be active'); } $activityData = null; @@ -375,45 +397,45 @@ final class ActivityController extends AbstractController } $entity->setActivityType($activityType); - $entity->setDate(new DateTime('now')); + $entity->setDate(new \DateTime('now')); if ($request->query->has('activityData')) { $activityData = $request->query->get('activityData'); - if (array_key_exists('durationTime', $activityData) && $activityType->getDurationTimeVisible() > 0) { + if (\array_key_exists('durationTime', $activityData) && $activityType->getDurationTimeVisible() > 0) { $durationTimeInMinutes = $activityData['durationTime']; $hours = floor($durationTimeInMinutes / 60); $minutes = $durationTimeInMinutes % 60; - $duration = DateTime::createFromFormat('H:i', $hours . ':' . $minutes); + $duration = \DateTime::createFromFormat('H:i', $hours.':'.$minutes); if ($duration) { $entity->setDurationTime($duration); } } - if (array_key_exists('date', $activityData)) { - $date = DateTime::createFromFormat('Y-m-d', $activityData['date']); + if (\array_key_exists('date', $activityData)) { + $date = \DateTime::createFromFormat('Y-m-d', $activityData['date']); if ($date) { $entity->setDate($date); } } - if (array_key_exists('personsId', $activityData) && $activityType->getPersonsVisible() > 0) { + if (\array_key_exists('personsId', $activityData) && $activityType->getPersonsVisible() > 0) { foreach ($activityData['personsId'] as $personId) { $concernedPerson = $this->personRepository->find($personId); $entity->addPerson($concernedPerson); } } - if (array_key_exists('professionalsId', $activityData) && $activityType->getThirdPartiesVisible() > 0) { + if (\array_key_exists('professionalsId', $activityData) && $activityType->getThirdPartiesVisible() > 0) { foreach ($activityData['professionalsId'] as $professionalsId) { $professional = $this->thirdPartyRepository->find($professionalsId); $entity->addThirdParty($professional); } } - if (array_key_exists('usersId', $activityData) && $activityType->getUsersVisible() > 0) { + if (\array_key_exists('usersId', $activityData) && $activityType->getUsersVisible() > 0) { foreach ($activityData['usersId'] as $userId) { $user = $this->userRepository->find($userId); @@ -423,16 +445,16 @@ final class ActivityController extends AbstractController } } - if (array_key_exists('location', $activityData) && $activityType->getLocationVisible() > 0) { + if (\array_key_exists('location', $activityData) && $activityType->getLocationVisible() > 0) { $location = $this->locationRepository->find($activityData['location']); $entity->setLocation($location); } - if (array_key_exists('comment', $activityData) && $activityType->getCommentVisible() > 0) { + if (\array_key_exists('comment', $activityData) && $activityType->getCommentVisible() > 0) { $comment = new CommentEmbeddable(); $comment->setComment($activityData['comment']); $comment->setUserId($this->getUser()->getid()); - $comment->setDate(new DateTime('now')); + $comment->setDate(new \DateTime('now')); $entity->setComment($comment); } } @@ -504,6 +526,9 @@ final class ActivityController extends AbstractController ]); } + /** + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/select-type", name="chill_activity_activity_select_type") + */ public function selectTypeAction(Request $request): Response { $view = null; @@ -511,9 +536,9 @@ final class ActivityController extends AbstractController [$person, $accompanyingPeriod] = $this->getEntity($request); if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $view = 'ChillActivityBundle:Activity:selectTypeAccompanyingCourse.html.twig'; + $view = '@ChillActivity/Activity/selectTypeAccompanyingCourse.html.twig'; } elseif ($person instanceof Person) { - $view = 'ChillActivityBundle:Activity:selectTypePerson.html.twig'; + $view = '@ChillActivity/Activity/selectTypePerson.html.twig'; } $data = []; @@ -548,6 +573,9 @@ final class ActivityController extends AbstractController ]); } + /** + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/{id}/show", name="chill_activity_activity_show") + */ public function showAction(Request $request, int $id): Response { $entity = $this->activityRepository->find($id); @@ -560,11 +588,11 @@ final class ActivityController extends AbstractController $person = $entity->getPerson(); if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig'; + $view = '@ChillActivity/Activity/showAccompanyingCourse.html.twig'; } elseif ($person instanceof Person) { - $view = 'ChillActivityBundle:Activity:showPerson.html.twig'; + $view = '@ChillActivity/Activity/showPerson.html.twig'; } else { - throw new RuntimeException('the activity should be linked with a period or person'); + throw new \RuntimeException('the activity should be linked with a period or person'); } if (null !== $accompanyingPeriod) { @@ -587,10 +615,6 @@ final class ActivityController extends AbstractController $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); */ - if (null === $view) { - throw $this->createNotFoundException('Template not found'); - } - return $this->render($view, [ 'person' => $person, 'accompanyingCourse' => $accompanyingPeriod, diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php index 32cec74a5..ce9dc64b3 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php @@ -24,6 +24,8 @@ class ActivityReasonCategoryController extends AbstractController { /** * Creates a new ActivityReasonCategory entity. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/create", name="chill_activity_activityreasoncategory_create", methods={"POST"}) */ public function createAction(Request $request) { @@ -31,15 +33,15 @@ class ActivityReasonCategoryController extends AbstractController $form = $this->createCreateForm($entity); $form->handleRequest($request); - if ($form->isValid()) { + if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($entity); $em->flush(); - return $this->redirect($this->generateUrl('chill_activity_activityreasoncategory_show', ['id' => $entity->getId()])); + return $this->redirectToRoute('chill_activity_activityreasoncategory_show', ['id' => $entity->getId()]); } - return $this->render('ChillActivityBundle:ActivityReasonCategory:new.html.twig', [ + return $this->render('@ChillActivity/ActivityReasonCategory/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), ]); @@ -48,13 +50,13 @@ class ActivityReasonCategoryController extends AbstractController /** * Displays a form to edit an existing ActivityReasonCategory entity. * - * @param mixed $id + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/{id}/edit", name="chill_activity_activityreasoncategory_edit") */ - public function editAction($id) + public function editAction(mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReasonCategory::class)->find($id); + $entity = $em->getRepository(ActivityReasonCategory::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.'); @@ -62,7 +64,7 @@ class ActivityReasonCategoryController extends AbstractController $editForm = $this->createEditForm($entity); - return $this->render('ChillActivityBundle:ActivityReasonCategory:edit.html.twig', [ + return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), ]); @@ -70,27 +72,31 @@ class ActivityReasonCategoryController extends AbstractController /** * Lists all ActivityReasonCategory entities. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/", name="chill_activity_activityreasoncategory") */ public function indexAction() { $em = $this->getDoctrine()->getManager(); - $entities = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReasonCategory::class)->findAll(); + $entities = $em->getRepository(ActivityReasonCategory::class)->findAll(); - return $this->render('ChillActivityBundle:ActivityReasonCategory:index.html.twig', [ + return $this->render('@ChillActivity/ActivityReasonCategory/index.html.twig', [ 'entities' => $entities, ]); } /** * Displays a form to create a new ActivityReasonCategory entity. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/new", name="chill_activity_activityreasoncategory_new") */ public function newAction() { $entity = new ActivityReasonCategory(); $form = $this->createCreateForm($entity); - return $this->render('ChillActivityBundle:ActivityReasonCategory:new.html.twig', [ + return $this->render('@ChillActivity/ActivityReasonCategory/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), ]); @@ -99,19 +105,19 @@ class ActivityReasonCategoryController extends AbstractController /** * Finds and displays a ActivityReasonCategory entity. * - * @param mixed $id + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/{id}/show", name="chill_activity_activityreasoncategory_show") */ - public function showAction($id) + public function showAction(mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReasonCategory::class)->find($id); + $entity = $em->getRepository(ActivityReasonCategory::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.'); } - return $this->render('ChillActivityBundle:ActivityReasonCategory:show.html.twig', [ + return $this->render('@ChillActivity/ActivityReasonCategory/show.html.twig', [ 'entity' => $entity, ]); } @@ -119,13 +125,13 @@ class ActivityReasonCategoryController extends AbstractController /** * Edits an existing ActivityReasonCategory entity. * - * @param mixed $id + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/{id}/update", name="chill_activity_activityreasoncategory_update", methods={"POST", "PUT"}) */ - public function updateAction(Request $request, $id) + public function updateAction(Request $request, mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReasonCategory::class)->find($id); + $entity = $em->getRepository(ActivityReasonCategory::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.'); @@ -134,13 +140,13 @@ class ActivityReasonCategoryController extends AbstractController $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); - if ($editForm->isValid()) { + if ($editForm->isSubmitted() && $editForm->isValid()) { $em->flush(); - return $this->redirect($this->generateUrl('chill_activity_activityreasoncategory_edit', ['id' => $id])); + return $this->redirectToRoute('chill_activity_activityreasoncategory_edit', ['id' => $id]); } - return $this->render('ChillActivityBundle:ActivityReasonCategory:edit.html.twig', [ + return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), ]); diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php index 95a49d0ba..323b27d25 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php @@ -24,15 +24,14 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; */ class ActivityReasonController extends AbstractController { - private ActivityReasonRepository $activityReasonRepository; - - public function __construct(ActivityReasonRepository $activityReasonRepository) + public function __construct(private readonly ActivityReasonRepository $activityReasonRepository) { - $this->activityReasonRepository = $activityReasonRepository; } /** * Creates a new ActivityReason entity. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/create", name="chill_activity_activityreason_create", methods={"POST"}) */ public function createAction(Request $request) { @@ -40,15 +39,15 @@ class ActivityReasonController extends AbstractController $form = $this->createCreateForm($entity); $form->handleRequest($request); - if ($form->isValid()) { + if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($entity); $em->flush(); - return $this->redirect($this->generateUrl('chill_activity_activityreason', ['id' => $entity->getId()])); + return $this->redirectToRoute('chill_activity_activityreason', ['id' => $entity->getId()]); } - return $this->render('ChillActivityBundle:ActivityReason:new.html.twig', [ + return $this->render('@ChillActivity/ActivityReason/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), ]); @@ -57,13 +56,13 @@ class ActivityReasonController extends AbstractController /** * Displays a form to edit an existing ActivityReason entity. * - * @param mixed $id + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/{id}/edit", name="chill_activity_activityreason_edit") */ - public function editAction($id) + public function editAction(mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)->find($id); + $entity = $em->getRepository(ActivityReason::class)->find($id); if (null === $entity) { throw new NotFoundHttpException('Unable to find ActivityReason entity.'); @@ -71,7 +70,7 @@ class ActivityReasonController extends AbstractController $editForm = $this->createEditForm($entity); - return $this->render('ChillActivityBundle:ActivityReason:edit.html.twig', [ + return $this->render('@ChillActivity/ActivityReason/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), ]); @@ -79,6 +78,8 @@ class ActivityReasonController extends AbstractController /** * Lists all ActivityReason entities. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/", name="chill_activity_activityreason") */ public function indexAction() { @@ -86,20 +87,22 @@ class ActivityReasonController extends AbstractController $entities = $this->activityReasonRepository->findAll(); - return $this->render('ChillActivityBundle:ActivityReason:index.html.twig', [ + return $this->render('@ChillActivity/ActivityReason/index.html.twig', [ 'entities' => $entities, ]); } /** * Displays a form to create a new ActivityReason entity. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/new", name="chill_activity_activityreason_new") */ public function newAction() { $entity = new ActivityReason(); $form = $this->createCreateForm($entity); - return $this->render('ChillActivityBundle:ActivityReason:new.html.twig', [ + return $this->render('@ChillActivity/ActivityReason/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), ]); @@ -108,19 +111,19 @@ class ActivityReasonController extends AbstractController /** * Finds and displays a ActivityReason entity. * - * @param mixed $id + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/{id}/show", name="chill_activity_activityreason_show") */ - public function showAction($id) + public function showAction(mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)->find($id); + $entity = $em->getRepository(ActivityReason::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find ActivityReason entity.'); } - return $this->render('ChillActivityBundle:ActivityReason:show.html.twig', [ + return $this->render('@ChillActivity/ActivityReason/show.html.twig', [ 'entity' => $entity, ]); } @@ -128,13 +131,13 @@ class ActivityReasonController extends AbstractController /** * Edits an existing ActivityReason entity. * - * @param mixed $id + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/{id}/update", name="chill_activity_activityreason_update", methods={"POST", "PUT"}) */ - public function updateAction(Request $request, $id) + public function updateAction(Request $request, mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)->find($id); + $entity = $em->getRepository(ActivityReason::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find ActivityReason entity.'); @@ -143,13 +146,13 @@ class ActivityReasonController extends AbstractController $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); - if ($editForm->isValid()) { + if ($editForm->isSubmitted() && $editForm->isValid()) { $em->flush(); - return $this->redirect($this->generateUrl('chill_activity_activityreason', ['id' => $id])); + return $this->redirectToRoute('chill_activity_activityreason', ['id' => $id]); } - return $this->render('ChillActivityBundle:ActivityReason:edit.html.twig', [ + return $this->render('@ChillActivity/ActivityReason/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), ]); diff --git a/src/Bundle/ChillActivityBundle/Controller/AdminActivityPresenceController.php b/src/Bundle/ChillActivityBundle/Controller/AdminActivityPresenceController.php index 3a39ca072..8a1ccda44 100644 --- a/src/Bundle/ChillActivityBundle/Controller/AdminActivityPresenceController.php +++ b/src/Bundle/ChillActivityBundle/Controller/AdminActivityPresenceController.php @@ -24,7 +24,7 @@ class AdminActivityPresenceController extends CRUDController */ protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { - /** @var \Doctrine\ORM\QueryBuilder $query */ + /* @var \Doctrine\ORM\QueryBuilder $query */ return $query->orderBy('e.id', 'ASC'); } } diff --git a/src/Bundle/ChillActivityBundle/Controller/AdminActivityTypeCategoryController.php b/src/Bundle/ChillActivityBundle/Controller/AdminActivityTypeCategoryController.php index 666f8721c..887749406 100644 --- a/src/Bundle/ChillActivityBundle/Controller/AdminActivityTypeCategoryController.php +++ b/src/Bundle/ChillActivityBundle/Controller/AdminActivityTypeCategoryController.php @@ -24,7 +24,7 @@ class AdminActivityTypeCategoryController extends CRUDController */ protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { - /** @var \Doctrine\ORM\QueryBuilder $query */ + /* @var \Doctrine\ORM\QueryBuilder $query */ return $query->orderBy('e.ordering', 'ASC'); } } diff --git a/src/Bundle/ChillActivityBundle/Controller/AdminActivityTypeController.php b/src/Bundle/ChillActivityBundle/Controller/AdminActivityTypeController.php index 5f245e9c4..ff75e5909 100644 --- a/src/Bundle/ChillActivityBundle/Controller/AdminActivityTypeController.php +++ b/src/Bundle/ChillActivityBundle/Controller/AdminActivityTypeController.php @@ -24,7 +24,7 @@ class AdminActivityTypeController extends CRUDController */ protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { - /** @var \Doctrine\ORM\QueryBuilder $query */ + /* @var \Doctrine\ORM\QueryBuilder $query */ return $query->orderBy('e.ordering', 'ASC') ->addOrderBy('e.id', 'ASC'); } diff --git a/src/Bundle/ChillActivityBundle/Controller/AdminController.php b/src/Bundle/ChillActivityBundle/Controller/AdminController.php index 4f96bb36f..deca96bd7 100644 --- a/src/Bundle/ChillActivityBundle/Controller/AdminController.php +++ b/src/Bundle/ChillActivityBundle/Controller/AdminController.php @@ -18,11 +18,18 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; */ class AdminController extends AbstractController { + /** + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activity", name="chill_activity_admin_index") + */ public function indexActivityAction() { - return $this->render('ChillActivityBundle:Admin:layout_activity.html.twig'); + return $this->render('@ChillActivity/Admin/layout_activity.html.twig'); } + /** + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activity_redirect_to_main", name="chill_admin_aside_activity_redirect_to_admin_index", options={null}) + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activity_redirect_to_main", name="chill_admin_activity_redirect_to_admin_index") + */ public function redirectToAdminIndexAction() { return $this->redirectToRoute('chill_main_admin_central'); diff --git a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php index 82949d635..b205384e6 100644 --- a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php +++ b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php @@ -25,17 +25,11 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface { use \Symfony\Component\DependencyInjection\ContainerAwareTrait; - private EntityManagerInterface $em; + private readonly \Faker\Generator $faker; - /** - * @var \Faker\Generator - */ - private $faker; - - public function __construct(EntityManagerInterface $em) + public function __construct(private readonly EntityManagerInterface $em) { $this->faker = FakerFactory::create('fr_FR'); - $this->em = $em; } public function getOrder() diff --git a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityReason.php b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityReason.php index d006a624c..af674be9e 100644 --- a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityReason.php +++ b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityReason.php @@ -52,13 +52,13 @@ class LoadActivityReason extends AbstractFixture implements OrderedFixtureInterf ]; foreach ($reasons as $r) { - echo 'Creating activity reason : ' . $r['name']['en'] . "\n"; + echo 'Creating activity reason : '.$r['name']['en']."\n"; $activityReason = (new ActivityReason()) - ->setName(($r['name'])) + ->setName($r['name']) ->setActive(true) ->setCategory($this->getReference($r['category'])); $manager->persist($activityReason); - $reference = 'activity_reason_' . $r['name']['en']; + $reference = 'activity_reason_'.$r['name']['en']; $this->addReference($reference, $activityReason); static::$references[] = $reference; } diff --git a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityReasonCategory.php b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityReasonCategory.php index 63d9a2ee0..a78ae3972 100644 --- a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityReasonCategory.php +++ b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityReasonCategory.php @@ -34,13 +34,13 @@ class LoadActivityReasonCategory extends AbstractFixture implements OrderedFixtu ]; foreach ($categs as $c) { - echo 'Creating activity reason category : ' . $c['name']['en'] . "\n"; + echo 'Creating activity reason category : '.$c['name']['en']."\n"; $activityReasonCategory = (new ActivityReasonCategory()) - ->setName(($c['name'])) + ->setName($c['name']) ->setActive(true); $manager->persist($activityReasonCategory); $this->addReference( - 'cat_' . $c['name']['en'], + 'cat_'.$c['name']['en'], $activityReasonCategory ); } diff --git a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityType.php b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityType.php index d0cd9d2be..891f2c979 100644 --- a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityType.php +++ b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityType.php @@ -54,14 +54,14 @@ class LoadActivityType extends Fixture implements OrderedFixtureInterface ]; foreach ($types as $t) { - echo 'Creating activity type : ' . $t['name']['fr'] . ' (cat:' . $t['category'] . " \n"; + echo 'Creating activity type : '.$t['name']['fr'].' (cat:'.$t['category']." \n"; $activityType = (new ActivityType()) - ->setName(($t['name'])) - ->setCategory($this->getReference('activity_type_cat_' . $t['category'])) + ->setName($t['name']) + ->setCategory($this->getReference('activity_type_cat_'.$t['category'])) ->setSocialIssuesVisible(1) ->setSocialActionsVisible(1); $manager->persist($activityType); - $reference = 'activity_type_' . $t['name']['fr']; + $reference = 'activity_type_'.$t['name']['fr']; $this->addReference($reference, $activityType); static::$references[] = $reference; } diff --git a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityTypeCategory.php b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityTypeCategory.php index 4fb5a3e38..2f15a2676 100644 --- a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityTypeCategory.php +++ b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityTypeCategory.php @@ -42,13 +42,13 @@ class LoadActivityTypeCategory extends Fixture implements OrderedFixtureInterfac ]; foreach ($categories as $cat) { - echo 'Creating activity type category : ' . $cat['ref'] . "\n"; + echo 'Creating activity type category : '.$cat['ref']."\n"; $newCat = (new ActivityTypeCategory()) - ->setName(($cat['name'])); + ->setName($cat['name']); $manager->persist($newCat); - $reference = 'activity_type_cat_' . $cat['ref']; + $reference = 'activity_type_cat_'.$cat['ref']; $this->addReference($reference, $newCat); static::$references[] = $reference; diff --git a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivitytACL.php b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivitytACL.php index 0ffb2ed0e..e7c893cd9 100644 --- a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivitytACL.php +++ b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivitytACL.php @@ -20,8 +20,6 @@ use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use function in_array; - /** * Add a role CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE for all groups except administrative, * and a role CHILL_ACTIVITY_SEE for administrative. @@ -40,10 +38,10 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac foreach (LoadScopes::$references as $scopeRef) { $scope = $this->getReference($scopeRef); - //create permission group + // create permission group switch ($permissionsGroup->getName()) { case 'social': - if ($scope->getName()['en'] === 'administrative') { + if ('administrative' === $scope->getName()['en']) { break 2; // we do not want any power on administrative } @@ -51,7 +49,7 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac case 'administrative': case 'direction': - if (in_array($scope->getName()['en'], ['administrative', 'social'], true)) { + if (\in_array($scope->getName()['en'], ['administrative', 'social'], true)) { break 2; // we do not want any power on social or administrative } @@ -60,7 +58,7 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac printf( 'Adding CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE & CHILL_ACTIVITY_DELETE, and stats and list permissions to %s ' - . "permission group, scope '%s' \n", + ."permission group, scope '%s' \n", $permissionsGroup->getName(), $scope->getName()['en'] ); diff --git a/src/Bundle/ChillActivityBundle/DependencyInjection/ChillActivityExtension.php b/src/Bundle/ChillActivityBundle/DependencyInjection/ChillActivityExtension.php index 49b3927ed..15e7e7e4b 100644 --- a/src/Bundle/ChillActivityBundle/DependencyInjection/ChillActivityExtension.php +++ b/src/Bundle/ChillActivityBundle/DependencyInjection/ChillActivityExtension.php @@ -32,7 +32,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf $container->setParameter('chill_activity.form.time_duration', $config['form']['time_duration']); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services.yaml'); $loader->load('services/export.yaml'); $loader->load('services/repositories.yaml'); @@ -73,7 +73,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf */ public function prependRoutes(ContainerBuilder $container) { - //add routes for custom bundle + // add routes for custom bundle $container->prependExtensionConfig('chill_main', [ 'routing' => [ 'resources' => [ diff --git a/src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php index 57a1ec578..112d49f60 100644 --- a/src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php @@ -13,7 +13,6 @@ namespace Chill\ActivityBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; - use function is_int; /** @@ -59,7 +58,7 @@ class Configuration implements ConfigurationInterface ->info('The number of seconds of this duration. Must be an integer.') ->cannotBeEmpty() ->validate() - ->ifTrue(static fn ($data) => !is_int($data))->thenInvalid('The value %s is not a valid integer') + ->ifTrue(static fn ($data) => !\is_int($data))->thenInvalid('The value %s is not a valid integer') ->end() ->end() ->scalarNode('label') diff --git a/src/Bundle/ChillActivityBundle/Entity/Activity.php b/src/Bundle/ChillActivityBundle/Entity/Activity.php index 32ceb2e74..9407aecbe 100644 --- a/src/Bundle/ChillActivityBundle/Entity/Activity.php +++ b/src/Bundle/ChillActivityBundle/Entity/Activity.php @@ -36,7 +36,6 @@ use DateTime; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\SerializedName; @@ -46,11 +45,15 @@ use Symfony\Component\Validator\Constraints as Assert; * Class Activity. * * @ORM\Entity(repositoryClass="Chill\ActivityBundle\Repository\ActivityRepository") + * * @ORM\Table(name="activity") + * * @ORM\HasLifecycleCallbacks + * * @DiscriminatorMap(typeProperty="type", mapping={ * "activity": Activity::class * }) + * * @ActivityValidator\ActivityValidity * * TODO see if necessary @@ -65,69 +68,84 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac use TrackUpdateTrait; - public const SENTRECEIVED_RECEIVED = 'received'; + final public const SENTRECEIVED_RECEIVED = 'received'; - public const SENTRECEIVED_SENT = 'sent'; + final public const SENTRECEIVED_SENT = 'sent'; /** * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod") + * * @Groups({"read"}) */ private ?AccompanyingPeriod $accompanyingPeriod = null; /** * @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityType") + * * @Groups({"read", "docgen:read"}) + * * @SerializedName("activityType") + * * @ORM\JoinColumn(name="type_id") */ private ActivityType $activityType; /** * @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityPresence") + * * @Groups({"docgen:read"}) */ private ?ActivityPresence $attendee = null; /** * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_") + * * @Groups({"docgen:read"}) */ private CommentEmbeddable $comment; /** * @ORM\Column(type="datetime") + * * @Groups({"docgen:read"}) */ - private DateTime $date; + private \DateTime $date; /** * @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"}) + * * @Assert\Valid(traverse=true) + * + * @var Collection */ private Collection $documents; /** * @ORM\Column(type="time", nullable=true) */ - private ?DateTime $durationTime = null; + private ?\DateTime $durationTime = null; /** * @ORM\Column(type="boolean", options={"default": false}) + * * @Groups({"docgen:read"}) */ private bool $emergency = false; /** * @ORM\Id + * * @ORM\Column(name="id", type="integer") + * * @ORM\GeneratedValue(strategy="AUTO") + * * @Groups({"read", "docgen:read"}) */ private ?int $id = null; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location") + * * @groups({"read", "docgen:read"}) */ private ?Location $location = null; @@ -139,9 +157,12 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac /** * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person") + * * @Groups({"read", "docgen:read"}) + * + * @var Collection */ - private ?Collection $persons = null; + private Collection $persons; /** * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable", columnPrefix="privateComment_") @@ -150,58 +171,78 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac /** * @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason") + * * @Groups({"docgen:read"}) + * + * @var Collection */ private Collection $reasons; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope") + * * @Groups({"docgen:read"}) */ private ?Scope $scope = null; /** * @ORM\Column(type="string", options={"default": ""}) + * * @Groups({"docgen:read"}) */ private string $sentReceived = ''; /** * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialAction") + * * @ORM\JoinTable(name="chill_activity_activity_chill_person_socialaction") + * * @Groups({"read", "docgen:read"}) + * + * @var Collection */ private Collection $socialActions; /** * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue") + * * @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue") + * * @Groups({"read", "docgen:read"}) + * + * @var Collection */ private Collection $socialIssues; /** * @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty") + * * @Groups({"read", "docgen:read"}) + * + * @var Collection */ - private ?Collection $thirdParties = null; + private Collection $thirdParties; /** * @ORM\Column(type="time", nullable=true) */ - private ?DateTime $travelTime = null; + private ?\DateTime $travelTime = null; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") + * * @Groups({"docgen:read"}) */ private ?User $user = null; /** * @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User") + * * @Groups({"read", "docgen:read"}) + * + * @var Collection */ - private ?Collection $users = null; + private Collection $users; public function __construct() { @@ -268,7 +309,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac $this->socialIssues[] = $socialIssue; } - if ($this->getAccompanyingPeriod() !== null) { + if (null !== $this->getAccompanyingPeriod()) { $this->getAccompanyingPeriod()->addSocialIssue($socialIssue); } @@ -334,7 +375,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this->comment; } - public function getDate(): DateTime + public function getDate(): \DateTime { return $this->date; } @@ -356,7 +397,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return (int) round(($this->durationTime->getTimestamp() + $this->durationTime->getOffset()) / 60.0, 0); } - public function getDurationTime(): ?DateTime + public function getDurationTime(): ?\DateTime { return $this->durationTime; } @@ -410,7 +451,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac // TODO better semantic with: return $this->persons->filter(...); foreach ($this->persons as $person) { - if ($this->accompanyingPeriod->getOpenParticipationContainsPerson($person) === null) { + if (null === $this->accompanyingPeriod->getOpenParticipationContainsPerson($person)) { $personsNotAssociated[] = $person; } } @@ -469,7 +510,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this->thirdParties; } - public function getTravelTime(): ?DateTime + public function getTravelTime(): ?\DateTime { return $this->travelTime; } @@ -580,7 +621,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this; } - public function setDate(DateTime $date): self + public function setDate(\DateTime $date): self { $this->date = $date; @@ -594,7 +635,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this; } - public function setDurationTime(?DateTime $durationTime): self + public function setDurationTime(?\DateTime $durationTime): self { $this->durationTime = $durationTime; @@ -664,7 +705,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this; } - public function setTravelTime(DateTime $travelTime): self + public function setTravelTime(\DateTime $travelTime): self { $this->travelTime = $travelTime; diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php b/src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php index b0154e509..c181b7c6b 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php @@ -18,7 +18,9 @@ use Symfony\Component\Serializer\Annotation as Serializer; * Class ActivityPresence. * * @ORM\Entity + * * @ORM\Table(name="activitytpresence") + * * @ORM\HasLifecycleCallbacks */ class ActivityPresence @@ -30,15 +32,20 @@ class ActivityPresence /** * @ORM\Id + * * @ORM\Column(name="id", type="integer") + * * @ORM\GeneratedValue(strategy="AUTO") + * * @Serializer\Groups({"docgen:read"}) */ private ?int $id = null; /** * @ORM\Column(type="json") + * * @Serializer\Groups({"docgen:read"}) + * * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) */ private array $name = []; diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityReason.php b/src/Bundle/ChillActivityBundle/Entity/ActivityReason.php index e6da6b7e0..90d088f6f 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityReason.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityReason.php @@ -17,39 +17,38 @@ use Doctrine\ORM\Mapping as ORM; * Class ActivityReason. * * @ORM\Entity + * * @ORM\Table(name="activityreason") + * * @ORM\HasLifecycleCallbacks */ class ActivityReason { /** - * @var bool * @ORM\Column(type="boolean") */ - private $active = true; + private bool $active = true; /** - * @var ActivityReasonCategory * @ORM\ManyToOne( * targetEntity="Chill\ActivityBundle\Entity\ActivityReasonCategory", * inversedBy="reasons") */ - private $category; + private ?ActivityReasonCategory $category = null; /** - * @var int - * * @ORM\Id + * * @ORM\Column(name="id", type="integer") + * * @ORM\GeneratedValue(strategy="AUTO") */ - private $id; + private ?int $id = null; /** - * @var array * @ORM\Column(type="json") */ - private $name; + private array $name; /** * Get active. @@ -81,27 +80,9 @@ class ActivityReason /** * Get name. - * - * @param mixed|null $locale - * - * @return array | string */ - public function getName($locale = null) + public function getName(): array { - if ($locale) { - if (isset($this->name[$locale])) { - return $this->name[$locale]; - } - - foreach ($this->name as $name) { - if (!empty($name)) { - return $name; - } - } - - return ''; - } - return $this->name; } diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php b/src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php index e8c2e245d..64d7f9672 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php @@ -12,34 +12,37 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** * Class ActivityReasonCategory. * * @ORM\Entity + * * @ORM\Table(name="activityreasoncategory") + * * @ORM\HasLifecycleCallbacks */ -class ActivityReasonCategory +class ActivityReasonCategory implements \Stringable { /** - * @var bool * @ORM\Column(type="boolean") */ - private $active = true; + private bool $active = true; /** - * @var int - * * @ORM\Id + * * @ORM\Column(name="id", type="integer") + * * @ORM\GeneratedValue(strategy="AUTO") */ - private $id; + private ?int $id = null; /** * @var string + * * @ORM\Column(type="json") */ private $name; @@ -47,12 +50,13 @@ class ActivityReasonCategory /** * Array of ActivityReason. * - * @var ArrayCollection + * @var Collection + * * @ORM\OneToMany( * targetEntity="Chill\ActivityBundle\Entity\ActivityReason", * mappedBy="category") */ - private $reasons; + private Collection $reasons; /** * ActivityReasonCategory constructor. @@ -62,12 +66,9 @@ class ActivityReasonCategory $this->reasons = new ArrayCollection(); } - /** - * @return string - */ - public function __toString() + public function __toString(): string { - return 'ActivityReasonCategory(' . $this->getName('x') . ')'; + return 'ActivityReasonCategory('.$this->getName('x').')'; } /** @@ -121,11 +122,9 @@ class ActivityReasonCategory * as unactive, all the reason have this entity as category is also * set as unactive. * - * @param bool $active - * * @return ActivityReasonCategory */ - public function setActive($active) + public function setActive(bool $active) { if ($this->active !== $active && !$active) { foreach ($this->reasons as $reason) { diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityType.php b/src/Bundle/ChillActivityBundle/Entity/ActivityType.php index ed8260a4d..96c369b39 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityType.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Entity; use Doctrine\ORM\Mapping as ORM; -use InvalidArgumentException; use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; @@ -22,31 +21,36 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * Class ActivityType. * * @ORM\Entity + * * @ORM\Table(name="activitytype") + * * @ORM\HasLifecycleCallbacks */ class ActivityType { - public const FIELD_INVISIBLE = 0; + final public const FIELD_INVISIBLE = 0; - public const FIELD_OPTIONAL = 1; + final public const FIELD_OPTIONAL = 1; - public const FIELD_REQUIRED = 2; + final public const FIELD_REQUIRED = 2; /** * @deprecated not in use + * * @ORM\Column(type="string", nullable=false, options={"default": ""}) */ private string $accompanyingPeriodLabel = ''; /** * @deprecated not in use + * * @ORM\Column(type="smallint", nullable=false, options={"default": 1}) */ private int $accompanyingPeriodVisible = self::FIELD_INVISIBLE; /** * @ORM\Column(type="boolean") + * * @Groups({"read"}) */ private bool $active = true; @@ -118,8 +122,11 @@ class ActivityType /** * @ORM\Id + * * @ORM\Column(name="id", type="integer") + * * @ORM\GeneratedValue(strategy="AUTO") + * * @Groups({"docgen:read"}) */ private ?int $id = null; @@ -136,7 +143,9 @@ class ActivityType /** * @ORM\Column(type="json") + * * @Groups({"read", "docgen:read"}) + * * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) */ private array $name = []; @@ -158,6 +167,7 @@ class ActivityType /** * @ORM\Column(type="smallint", nullable=false, options={"default": 1}) + * * @Groups({"read"}) */ private int $personsVisible = self::FIELD_OPTIONAL; @@ -238,6 +248,7 @@ class ActivityType /** * @ORM\Column(type="smallint", nullable=false, options={"default": 1}) + * * @Groups({"read"}) */ private int $thirdPartiesVisible = self::FIELD_INVISIBLE; @@ -264,6 +275,7 @@ class ActivityType /** * @ORM\Column(type="smallint", nullable=false, options={"default": 1}) + * * @Groups({"read"}) */ private int $usersVisible = self::FIELD_OPTIONAL; @@ -275,13 +287,15 @@ class ActivityType /** * @Assert\Callback - * - * @param mixed $payload */ - public function checkSocialActionsVisibility(ExecutionContextInterface $context, $payload) + public function checkSocialActionsVisibility(ExecutionContextInterface $context, mixed $payload) { if ($this->socialIssuesVisible !== $this->socialActionsVisible) { - if (!(2 === $this->socialIssuesVisible && 1 === $this->socialActionsVisible)) { + // if social issues are invisible then social actions cannot be optional or required + if social issues are optional then social actions shouldn't be required + if ( + (0 === $this->socialIssuesVisible && (1 === $this->socialActionsVisible || 2 === $this->socialActionsVisible)) + || (1 === $this->socialIssuesVisible && 2 === $this->socialActionsVisible) + ) { $context ->buildViolation('The socialActionsVisible value is not compatible with the socialIssuesVisible value') ->atPath('socialActionsVisible') @@ -374,13 +388,13 @@ class ActivityType public function getLabel(string $field): ?string { - $property = $field . 'Label'; + $property = $field.'Label'; if (!property_exists($this, $property)) { - throw new InvalidArgumentException('Field "' . $field . '" not found'); + throw new \InvalidArgumentException('Field "'.$field.'" not found'); } - /** @phpstan-ignore-next-line */ + /* @phpstan-ignore-next-line */ return $this->{$property}; } @@ -533,25 +547,25 @@ class ActivityType public function isRequired(string $field): bool { - $property = $field . 'Visible'; + $property = $field.'Visible'; if (!property_exists($this, $property)) { - throw new InvalidArgumentException('Field "' . $field . '" not found'); + throw new \InvalidArgumentException('Field "'.$field.'" not found'); } - /** @phpstan-ignore-next-line */ + /* @phpstan-ignore-next-line */ return self::FIELD_REQUIRED === $this->{$property}; } public function isVisible(string $field): bool { - $property = $field . 'Visible'; + $property = $field.'Visible'; if (!property_exists($this, $property)) { - throw new InvalidArgumentException('Field "' . $field . '" not found'); + throw new \InvalidArgumentException('Field "'.$field.'" not found'); } - /** @phpstan-ignore-next-line */ + /* @phpstan-ignore-next-line */ return self::FIELD_INVISIBLE !== $this->{$property}; } diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityTypeCategory.php b/src/Bundle/ChillActivityBundle/Entity/ActivityTypeCategory.php index 2cf6972c7..680a15fa7 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityTypeCategory.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityTypeCategory.php @@ -15,7 +15,9 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity + * * @ORM\Table(name="activitytypecategory") + * * @ORM\HasLifecycleCallbacks */ class ActivityTypeCategory @@ -27,7 +29,9 @@ class ActivityTypeCategory /** * @ORM\Id + * * @ORM\Column(name="id", type="integer") + * * @ORM\GeneratedValue(strategy="AUTO") */ private ?int $id = null; diff --git a/src/Bundle/ChillActivityBundle/EntityListener/ActivityEntityListener.php b/src/Bundle/ChillActivityBundle/EntityListener/ActivityEntityListener.php index 76fb440e7..fd9899c0b 100644 --- a/src/Bundle/ChillActivityBundle/EntityListener/ActivityEntityListener.php +++ b/src/Bundle/ChillActivityBundle/EntityListener/ActivityEntityListener.php @@ -15,21 +15,12 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; -use function in_array; - class ActivityEntityListener { - private EntityManagerInterface $em; - - private AccompanyingPeriodWorkRepository $workRepository; - - public function __construct(EntityManagerInterface $em, AccompanyingPeriodWorkRepository $workRepository) + public function __construct(private readonly EntityManagerInterface $em, private readonly AccompanyingPeriodWorkRepository $workRepository) { - $this->em = $em; - $this->workRepository = $workRepository; } public function persistActionToCourse(Activity $activity) @@ -39,11 +30,11 @@ class ActivityEntityListener $accompanyingCourseWorks = $this->workRepository->findByAccompanyingPeriod($period); $periodActions = []; - $now = new DateTimeImmutable(); + $now = new \DateTimeImmutable(); foreach ($accompanyingCourseWorks as $key => $work) { // take only the actions which are still opened - if ($work->getEndDate() === null || $work->getEndDate() > ($activity->getDate() ?? $now)) { + if (null === $work->getEndDate() || $work->getEndDate() > ($activity->getDate() ?? $now)) { $periodActions[$key] = spl_object_hash($work->getSocialAction()); } } @@ -52,14 +43,14 @@ class ActivityEntityListener $associatedThirdparties = $activity->getThirdParties(); foreach ($activity->getSocialActions() as $action) { - if (in_array(spl_object_hash($action), $periodActions, true)) { + if (\in_array(spl_object_hash($action), $periodActions, true)) { continue; } $newAction = new AccompanyingPeriodWork(); $newAction->setSocialAction($action); $period->addWork($newAction); - $date = DateTimeImmutable::createFromMutable($activity->getDate()); + $date = \DateTimeImmutable::createFromMutable($activity->getDate()); $newAction->setStartDate($date); foreach ($associatedPersons as $person) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php index c23db738e..e9e8fb474 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php @@ -27,7 +27,7 @@ class ByActivityNumberAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data): void { $qb - ->addSelect('(SELECT COUNT(activity.id) FROM ' . Activity::class . ' activity WHERE activity.accompanyingPeriod = acp) AS activity_by_number_aggregator') + ->addSelect('(SELECT COUNT(activity.id) FROM '.Activity::class.' activity WHERE activity.accompanyingPeriod = acp) AS activity_by_number_aggregator') ->addGroupBy('activity_by_number_aggregator'); } @@ -40,6 +40,7 @@ class ByActivityNumberAggregator implements AggregatorInterface { // No form needed } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php new file mode 100644 index 000000000..0f41547d9 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php @@ -0,0 +1,123 @@ +add('after_date', PickRollingDateType::class, [ + 'required' => false, + 'label' => 'export.aggregator.acp.by_activity_type.after_date', + ]) + ->add('before_date', PickRollingDateType::class, [ + 'required' => false, + 'label' => 'export.aggregator.acp.by_activity_type.before_date', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'before_date' => null, + 'after_date' => null, + ]; + } + + public function getLabels($key, array $values, mixed $data) + { + return function (int|string|null $value): string { + if ('_header' === $value) { + return 'export.aggregator.acp.by_activity_type.activity_type'; + } + + if ('' === $value || null === $value || null === $activityType = $this->activityTypeRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($activityType->getName()); + }; + } + + public function getQueryKeys($data) + { + return [self::PREFIX.'_actype_id']; + } + + public function getTitle() + { + return 'export.aggregator.acp.by_activity_type.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::PREFIX; + + // we make a left join, with acp having at least one activity of the given type + $exists = 'EXISTS (SELECT 1 FROM '.Activity::class." {$p}_activity WHERE {$p}_activity.accompanyingPeriod = acp AND {$p}_activity.activityType = {$p}_activity_type"; + + if (null !== $data['after_date']) { + $exists .= " AND {$p}_activity.date > :{$p}_after_date"; + $qb->setParameter("{$p}_after_date", $this->rollingDateConverter->convert($data['after_date'])); + } + + if (null !== $data['before_date']) { + $exists .= " AND {$p}_activity.date < :{$p}_before_date"; + $qb->setParameter("{$p}_before_date", $this->rollingDateConverter->convert($data['before_date'])); + } + + $exists .= ')'; + + $qb->leftJoin( + ActivityType::class, + "{$p}_activity_type", + Join::WITH, + $exists + ); + + $qb + ->addSelect("{$p}_activity_type.id AS {$p}_actype_id") + ->addGroupBy("{$p}_actype_id"); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php index 1a75357ae..157f4c023 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php @@ -17,20 +17,11 @@ use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class BySocialActionAggregator implements AggregatorInterface { - private SocialActionRender $actionRender; - - private SocialActionRepository $actionRepository; - - public function __construct( - SocialActionRender $actionRender, - SocialActionRepository $actionRepository - ) { - $this->actionRender = $actionRender; - $this->actionRepository = $actionRepository; + public function __construct(private readonly SocialActionRender $actionRender, private readonly SocialActionRepository $actionRepository) + { } public function addRole(): ?string @@ -40,7 +31,7 @@ class BySocialActionAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actsocialaction', $qb->getAllAliases(), true)) { + if (!\in_array('actsocialaction', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.socialActions', 'actsocialaction'); } @@ -57,6 +48,7 @@ class BySocialActionAggregator implements AggregatorInterface { // no form } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php index 9100a8c8f..94f6506d1 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php @@ -17,20 +17,11 @@ use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class BySocialIssueAggregator implements AggregatorInterface { - private SocialIssueRender $issueRender; - - private SocialIssueRepository $issueRepository; - - public function __construct( - SocialIssueRepository $issueRepository, - SocialIssueRender $issueRender - ) { - $this->issueRepository = $issueRepository; - $this->issueRender = $issueRender; + public function __construct(private readonly SocialIssueRepository $issueRepository, private readonly SocialIssueRender $issueRender) + { } public function addRole(): ?string @@ -40,7 +31,7 @@ class BySocialIssueAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actsocialissue', $qb->getAllAliases(), true)) { + if (!\in_array('actsocialissue', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.socialIssues', 'actsocialissue'); } @@ -57,6 +48,7 @@ class BySocialIssueAggregator implements AggregatorInterface { // no form } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php new file mode 100644 index 000000000..ab07afca7 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php @@ -0,0 +1,76 @@ +getAllAliases(), true)) { + $qb->leftJoin('activity.location', 'actloc'); + } + $qb->addSelect(sprintf('actloc.name AS %s', self::KEY)); + $qb->addGroupBy(self::KEY); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form required for this aggregator + } + + public function getFormDefaultData(): array + { + return []; + } + + public function getLabels($key, array $values, $data): \Closure + { + return function ($value): string { + if ('_header' === $value) { + return 'export.aggregator.activity.by_location.Activity Location'; + } + + if (null === $value || '' === $value) { + return ''; + } + + return $value; + }; + } + + public function getQueryKeys($data): array + { + return [self::KEY]; + } + + public function getTitle() + { + return 'export.aggregator.activity.by_location.Title'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php new file mode 100644 index 000000000..392ff5b19 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php @@ -0,0 +1,76 @@ +activityPresenceRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($presence->getName()); + }; + } + + public function getQueryKeys($data) + { + return ['activity_presence_aggregator_attendee']; + } + + public function getTitle(): string + { + return 'export.aggregator.activity.by_activity_presence.Group activity by presence'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data): void + { + $qb->addSelect('IDENTITY(activity.attendee) AS activity_presence_aggregator_attendee'); + $qb->addGroupBy('activity_presence_aggregator_attendee'); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php similarity index 65% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php index 2537f2cf6..9945fd80a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Aggregator\PersonAggregators; +namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityReasonCategoryRepository; @@ -17,33 +17,19 @@ use Chill\ActivityBundle\Repository\ActivityReasonRepository; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; -use RuntimeException; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; -use function count; -use function in_array; - class ActivityReasonAggregator implements AggregatorInterface, ExportElementValidatedInterface { - protected ActivityReasonCategoryRepository $activityReasonCategoryRepository; - - protected ActivityReasonRepository $activityReasonRepository; - - protected TranslatableStringHelperInterface $translatableStringHelper; - public function __construct( - ActivityReasonCategoryRepository $activityReasonCategoryRepository, - ActivityReasonRepository $activityReasonRepository, - TranslatableStringHelper $translatableStringHelper + protected ActivityReasonCategoryRepository $activityReasonCategoryRepository, + protected ActivityReasonRepository $activityReasonRepository, + protected TranslatableStringHelper $translatableStringHelper ) { - $this->activityReasonCategoryRepository = $activityReasonCategoryRepository; - $this->activityReasonRepository = $activityReasonRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -61,37 +47,30 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali $elem = 'actreasoncat.id'; $alias = 'activity_categories_id'; } else { - throw new RuntimeException('The data provided are not recognized.'); + throw new \RuntimeException('The data provided are not recognized.'); } - $qb->addSelect($elem . ' as ' . $alias); + $qb->addSelect($elem.' as '.$alias); // make a jointure only if needed - if (!in_array('actreasons', $qb->getAllAliases(), true)) { - $qb->innerJoin('activity.reasons', 'actreasons'); + if (!\in_array('actreasons', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.reasons', 'actreasons'); } // join category if necessary if ('activity_categories_id' === $alias) { // add join only if needed - if (!in_array('actreasoncat', $qb->getAllAliases(), true)) { + if (!\in_array('actreasoncat', $qb->getAllAliases(), true)) { $qb->join('actreasons.category', 'actreasoncat'); } } - // add the "group by" part - $groupBy = $qb->getDQLPart('groupBy'); - - if (count($groupBy) > 0) { - $qb->addGroupBy($alias); - } else { - $qb->groupBy($alias); - } + $qb->addGroupBy($alias); } public function applyOn(): string { - return Declarations::ACTIVITY_PERSON; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -110,29 +89,16 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali ] ); } + public function getFormDefaultData(): array { - return []; + return [ + 'level' => 'reasons', + ]; } public function getLabels($key, array $values, $data) { - // for performance reason, we load data from db only once - switch ($data['level']) { - case 'reasons': - $this->activityReasonRepository->findBy(['id' => $values]); - - break; - - case 'categories': - $this->activityReasonCategoryRepository->findBy(['id' => $values]); - - break; - - default: - throw new RuntimeException(sprintf("The level data '%s' is invalid.", $data['level'])); - } - return function ($value) use ($data) { if ('_header' === $value) { return 'reasons' === $data['level'] ? 'Group by reasons' : 'Group by categories of reason'; @@ -171,7 +137,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali return ['activity_categories_id']; } - throw new RuntimeException('The data provided are not recognised.'); + throw new \RuntimeException('The data provided are not recognised.'); } public function getTitle() diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php index a74428b4a..d433e6a86 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php @@ -15,25 +15,15 @@ use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; -use Closure; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class ActivityTypeAggregator implements AggregatorInterface { - public const KEY = 'activity_type_aggregator'; + final public const KEY = 'activity_type_aggregator'; - protected ActivityTypeRepositoryInterface $activityTypeRepository; - - protected TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct( - ActivityTypeRepositoryInterface $activityTypeRepository, - TranslatableStringHelperInterface $translatableStringHelper - ) { - $this->activityTypeRepository = $activityTypeRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct(protected ActivityTypeRepositoryInterface $activityTypeRepository, protected TranslatableStringHelperInterface $translatableStringHelper) + { } public function addRole(): ?string @@ -43,7 +33,7 @@ class ActivityTypeAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('acttype', $qb->getAllAliases(), true)) { + if (!\in_array('acttype', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.activityType', 'acttype'); } @@ -60,27 +50,23 @@ class ActivityTypeAggregator implements AggregatorInterface { // no form required for this aggregator } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { - // for performance reason, we load data from db only once - $this->activityTypeRepository->findBy(['id' => $values]); - - return function ($value): string { + return function (int|string|null $value): string { if ('_header' === $value) { return 'Activity type'; } - if (null === $value || '' === $value) { + if (null === $value || '' === $value || null === $t = $this->activityTypeRepository->find($value)) { return ''; } - $t = $this->activityTypeRepository->find($value); - return $this->translatableStringHelper->localize($t->getName()); }; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php index 2fab2af83..46b931243 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php @@ -15,24 +15,15 @@ use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Templating\Entity\UserRender; -use Closure; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class ActivityUserAggregator implements AggregatorInterface { - public const KEY = 'activity_user_id'; + final public const KEY = 'activity_user_id'; - private UserRender $userRender; - - private UserRepository $userRepository; - - public function __construct( - UserRepository $userRepository, - UserRender $userRender - ) { - $this->userRepository = $userRepository; - $this->userRender = $userRender; + public function __construct(private readonly UserRepository $userRepository, private readonly UserRender $userRender) + { } public function addRole(): ?string @@ -58,12 +49,13 @@ class ActivityUserAggregator implements AggregatorInterface { // nothing to add } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, $values, $data): Closure + public function getLabels($key, $values, $data): \Closure { return function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php index e1e9f161d..c8ef24332 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php @@ -17,18 +17,11 @@ use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class ActivityUsersAggregator implements AggregatorInterface { - private UserRender $userRender; - - private UserRepositoryInterface $userRepository; - - public function __construct(UserRepositoryInterface $userRepository, UserRender $userRender) + public function __construct(private readonly UserRepositoryInterface $userRepository, private readonly UserRender $userRender) { - $this->userRepository = $userRepository; - $this->userRender = $userRender; } public function addRole(): ?string @@ -38,7 +31,7 @@ class ActivityUsersAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actusers', $qb->getAllAliases(), true)) { + if (!\in_array('actusers', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.users', 'actusers'); } @@ -56,6 +49,7 @@ class ActivityUsersAggregator implements AggregatorInterface { // nothing to add on the form } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php index 721078989..591406d59 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php @@ -12,22 +12,22 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserJobHistory; +use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; -class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorInterface +class ActivityUsersJobAggregator implements AggregatorInterface { - private TranslatableStringHelperInterface $translatableStringHelper; + private const PREFIX = 'act_agg_user_job'; - private UserJobRepositoryInterface $userJobRepository; - - public function __construct(UserJobRepositoryInterface $userJobRepository, TranslatableStringHelperInterface $translatableStringHelper) - { - $this->userJobRepository = $userJobRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly UserJobRepositoryInterface $userJobRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) { } public function addRole(): ?string @@ -37,24 +37,39 @@ class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorI public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actusers', $qb->getAllAliases(), true)) { - $qb->leftJoin('activity.users', 'actusers'); - } + $p = self::PREFIX; $qb - ->addSelect('IDENTITY(actusers.userJob) AS activity_users_job_aggregator') - ->addGroupBy('activity_users_job_aggregator'); + ->leftJoin('activity.users', "{$p}_user") + ->leftJoin( + UserJobHistory::class, + "{$p}_history", + Expr\Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // job_at based on activity.date + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'activity.date') + ) + ) + ) + ->addSelect("IDENTITY({$p}_history.job) AS {$p}_select") + ->addGroupBy("{$p}_select"); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { - // nothing to add in the form } + public function getFormDefaultData(): array { return []; @@ -81,11 +96,11 @@ class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorI public function getQueryKeys($data): array { - return ['activity_users_job_aggregator']; + return [self::PREFIX.'_select']; } - public function getTitle() + public function getTitle(): string { - return 'Aggregate by users job'; + return 'export.aggregator.activity.by_user_job.Aggregate by users job'; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php index d7932e9e8..c5d9175b4 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php @@ -12,22 +12,22 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; -class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\AggregatorInterface +class ActivityUsersScopeAggregator implements AggregatorInterface { - private ScopeRepositoryInterface $scopeRepository; + private const PREFIX = 'act_agg_user_scope'; - private TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct(ScopeRepositoryInterface $scopeRepository, TranslatableStringHelperInterface $translatableStringHelper) - { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly ScopeRepositoryInterface $scopeRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) { } public function addRole(): ?string @@ -37,24 +37,39 @@ class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\Aggregato public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actusers', $qb->getAllAliases(), true)) { - $qb->leftJoin('activity.users', 'actusers'); - } + $p = self::PREFIX; $qb - ->addSelect('IDENTITY(actusers.mainScope) AS activity_users_main_scope_aggregator') - ->addGroupBy('activity_users_main_scope_aggregator'); + ->leftJoin('activity.users', "{$p}_user") + ->leftJoin( + UserScopeHistory::class, + "{$p}_history", + Expr\Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // scope_at based on activity.date + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'activity.date') + ) + ) + ) + ->addSelect("IDENTITY({$p}_history.scope) AS {$p}_select") + ->addGroupBy("{$p}_select"); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { - // nothing to add in the form } + public function getFormDefaultData(): array { return []; @@ -81,11 +96,11 @@ class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\Aggregato public function getQueryKeys($data): array { - return ['activity_users_main_scope_aggregator']; + return [self::PREFIX.'_select']; } - public function getTitle() + public function getTitle(): string { - return 'Aggregate by users scope'; + return 'export.aggregator.activity.by_user_scope.Aggregate by users scope'; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php similarity index 80% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php index 917459de3..4e8ec44b0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; @@ -20,16 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface; class ByCreatorAggregator implements AggregatorInterface { - private UserRender $userRender; - - private UserRepositoryInterface $userRepository; - - public function __construct( - UserRepositoryInterface $userRepository, - UserRender $userRender - ) { - $this->userRepository = $userRepository; - $this->userRender = $userRender; + public function __construct(private readonly UserRepositoryInterface $userRepository, private readonly UserRender $userRender) + { } public function addRole(): ?string @@ -45,13 +37,14 @@ class ByCreatorAggregator implements AggregatorInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { // no form } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php similarity index 76% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php index ac05b153c..15762a4f0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; @@ -17,20 +17,11 @@ use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class ByThirdpartyAggregator implements AggregatorInterface { - private ThirdPartyRender $thirdPartyRender; - - private ThirdPartyRepository $thirdPartyRepository; - - public function __construct( - ThirdPartyRepository $thirdPartyRepository, - ThirdPartyRender $thirdPartyRender - ) { - $this->thirdPartyRepository = $thirdPartyRepository; - $this->thirdPartyRender = $thirdPartyRender; + public function __construct(private readonly ThirdPartyRepository $thirdPartyRepository, private readonly ThirdPartyRender $thirdPartyRender) + { } public function addRole(): ?string @@ -40,7 +31,7 @@ class ByThirdpartyAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('acttparty', $qb->getAllAliases(), true)) { + if (!\in_array('acttparty', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.thirdParties', 'acttparty'); } @@ -50,13 +41,14 @@ class ByThirdpartyAggregator implements AggregatorInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { // no form } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php new file mode 100644 index 000000000..98960aec6 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php @@ -0,0 +1,104 @@ +leftJoin('activity.createdBy', "{$p}_user") + ->leftJoin( + UserJobHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // job_at based on activity.date + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'activity.date') + ) + ) + ) + ->addSelect("IDENTITY({$p}_history.job) AS {$p}_select") + ->addGroupBy("{$p}_select"); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getFormDefaultData(): array + { + return []; + } + + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ('_header' === $value) { + return 'Job'; + } + + if (null === $value || '' === $value || null === $s = $this->userJobRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize( + $s->getLabel() + ); + }; + } + + public function getQueryKeys($data): array + { + return [self::PREFIX.'_select']; + } + + public function getTitle(): string + { + return 'export.aggregator.activity.by_creator_job.Group activity by creator job'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php similarity index 53% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php index a4b5258e2..f1fb4b2b6 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php @@ -9,28 +9,25 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class CreatorScopeAggregator implements AggregatorInterface { - private ScopeRepository $scopeRepository; - - private TranslatableStringHelper $translatableStringHelper; + private const PREFIX = 'acp_agg_creator_scope'; public function __construct( - ScopeRepository $scopeRepository, - TranslatableStringHelper $translatableStringHelper + private readonly ScopeRepository $scopeRepository, + private readonly TranslatableStringHelper $translatableStringHelper ) { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -40,23 +37,39 @@ class CreatorScopeAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actcreator', $qb->getAllAliases(), true)) { - $qb->leftJoin('activity.createdBy', 'actcreator'); - } + $p = self::PREFIX; - $qb->addSelect('IDENTITY(actcreator.mainScope) AS creatorscope_aggregator'); - $qb->addGroupBy('creatorscope_aggregator'); + $qb + ->leftJoin('activity.createdBy', "{$p}_user") + ->leftJoin( + UserScopeHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // scope_at based on activity.date + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'activity.date') + ) + ) + ) + ->addSelect("IDENTITY({$p}_history.scope) AS {$p}_select") + ->addGroupBy("{$p}_select"); } public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { - // no form } + public function getFormDefaultData(): array { return []; @@ -83,11 +96,11 @@ class CreatorScopeAggregator implements AggregatorInterface public function getQueryKeys($data): array { - return ['creatorscope_aggregator']; + return [self::PREFIX.'_select']; } public function getTitle(): string { - return 'Group activity by creator scope'; + return 'export.aggregator.activity.by_creator_scope.Group activity by creator scope'; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php similarity index 74% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php index 4ede5b4c4..9d9e5ed61 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php @@ -9,15 +9,13 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; use Doctrine\ORM\QueryBuilder; -use RuntimeException; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; class DateAggregator implements AggregatorInterface { @@ -56,7 +54,7 @@ class DateAggregator implements AggregatorInterface break; // order DESC does not works ! default: - throw new RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency'])); + throw new \RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency'])); } $qb->addSelect(sprintf("TO_CHAR(activity.date, '%s') AS date_aggregator", $fmt)); @@ -66,7 +64,7 @@ class DateAggregator implements AggregatorInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -75,9 +73,9 @@ class DateAggregator implements AggregatorInterface 'choices' => self::CHOICES, 'multiple' => false, 'expanded' => true, - 'empty_data' => self::DEFAULT_CHOICE, ]); } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; @@ -87,24 +85,16 @@ class DateAggregator implements AggregatorInterface { return static function ($value) use ($data): string { if ('_header' === $value) { - return 'by ' . $data['frequency']; + return 'by '.$data['frequency']; } if (null === $value) { return ''; } - switch ($data['frequency']) { - case 'month': - case 'week': - //return $this->translator->trans('for week') .' '. $value ; - - case 'year': - //return $this->translator->trans('in year') .' '. $value ; - - default: - return $value; - } + return match ($data['frequency']) { + default => $value, + }; }; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php similarity index 75% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php index c72609e2c..69c8a646c 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; @@ -17,20 +17,11 @@ use Chill\MainBundle\Repository\LocationTypeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class LocationTypeAggregator implements AggregatorInterface { - private LocationTypeRepository $locationTypeRepository; - - private TranslatableStringHelper $translatableStringHelper; - - public function __construct( - LocationTypeRepository $locationTypeRepository, - TranslatableStringHelper $translatableStringHelper - ) { - $this->locationTypeRepository = $locationTypeRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct(private readonly LocationTypeRepository $locationTypeRepository, private readonly TranslatableStringHelper $translatableStringHelper) + { } public function addRole(): ?string @@ -40,7 +31,7 @@ class LocationTypeAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actloc', $qb->getAllAliases(), true)) { + if (!\in_array('actloc', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.location', 'actloc'); } @@ -50,13 +41,14 @@ class LocationTypeAggregator implements AggregatorInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { // no form } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php new file mode 100644 index 000000000..96c330071 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php @@ -0,0 +1,67 @@ +labelPersonHelper->getLabel($key, $values, 'export.aggregator.person.by_person.person'); + } + + public function getQueryKeys($data) + { + return ['activity_by_person_agg']; + } + + public function getTitle() + { + return 'export.aggregator.person.by_person.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + ->addSelect('IDENTITY(activity.person) AS activity_by_person_agg') + ->addGroupBy('activity_by_person_agg'); + } + + public function applyOn() + { + return Declarations::ACTIVITY_PERSON; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php new file mode 100644 index 000000000..18ca489e4 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php @@ -0,0 +1,80 @@ +labelPersonHelper->getLabel($key, $values, 'export.aggregator.activity.by_persons.Persons'); + } + + public function getQueryKeys($data) + { + return [self::PREFIX.'_pid']; + } + + public function getTitle() + { + return 'export.aggregator.activity.by_persons.Group activity by persons'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::PREFIX; + + $qb + ->leftJoin('activity.persons', "{$p}_p") + ->addSelect("{$p}_p.id AS {$p}_pid") + ->addGroupBy("{$p}_pid"); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php index ae1dae375..ec436ef61 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php @@ -14,17 +14,13 @@ namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; use Doctrine\ORM\QueryBuilder; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Contracts\Translation\TranslatorInterface; class SentReceivedAggregator implements AggregatorInterface { - private TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) + public function __construct(private readonly TranslatorInterface $translator) { - $this->translator = $translator; } public function addRole(): ?string @@ -47,6 +43,7 @@ class SentReceivedAggregator implements AggregatorInterface { // No form needed } + public function getFormDefaultData(): array { return []; @@ -71,7 +68,7 @@ class SentReceivedAggregator implements AggregatorInterface return $this->translator->trans('export.aggregator.activity.by_sent_received.is received'); default: - throw new LogicException(sprintf('The value %s is not valid', $value)); + throw new \LogicException(sprintf('The value %s is not valid', $value)); } }; } diff --git a/src/Bundle/ChillActivityBundle/Export/Declarations.php b/src/Bundle/ChillActivityBundle/Export/Declarations.php index 79afb09c8..210988770 100644 --- a/src/Bundle/ChillActivityBundle/Export/Declarations.php +++ b/src/Bundle/ChillActivityBundle/Export/Declarations.php @@ -16,9 +16,9 @@ namespace Chill\ActivityBundle\Export; */ abstract class Declarations { - public const ACTIVITY = 'activity'; + final public const ACTIVITY = 'activity'; - public const ACTIVITY_ACP = 'activity_linked_to_acp'; + final public const ACTIVITY_ACP = 'activity_linked_to_acp'; - public const ACTIVITY_PERSON = 'activity_linked_to_person'; + final public const ACTIVITY_PERSON = 'activity_linked_to_person'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php index 6930784d3..96fdfa095 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php @@ -11,34 +11,35 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Export\LinkedToACP; -use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; +use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\Person\PersonCenterHistory; use Chill\PersonBundle\Export\Declarations as PersonDeclarations; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; class AvgActivityDuration implements ExportInterface, GroupedExportInterface { - protected EntityRepository $repository; + private readonly bool $filterStatsByCenters; public function __construct( - EntityManagerInterface $em + private readonly ActivityRepository $activityRepository, + ParameterBagInterface $parameterBag, ) { - $this->repository = $em->getRepository(Activity::class); + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -62,7 +63,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { if ('export_avg_activity_duration' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period duration' : $value; @@ -92,23 +93,27 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface { $centers = array_map(static fn ($el) => $el['center'], $acl); - $qb = $this->repository->createQueryBuilder('activity'); + $qb = $this->activityRepository->createQueryBuilder('activity'); $qb ->join('activity.accompanyingPeriod', 'acp') ->select('AVG(activity.durationTime) as export_avg_activity_duration') ->andWhere($qb->expr()->isNotNull('activity.durationTime')); - $qb - ->andWhere( - $qb->expr()->exists( - 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part - JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + if ($this->filterStatsByCenters) { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part + JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) ' + ) ) - ) - ->setParameter('authorized_centers', $centers); + ->setParameter('authorized_centers', $centers); + } + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php index f1e926c64..acfb073f5 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Export\LinkedToACP; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -23,23 +24,28 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterface { protected EntityRepository $repository; + private readonly bool $filterStatsByCenters; + public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, + ParameterBagInterface $parameterBag, ) { $this->repository = $em->getRepository(Activity::class); + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } public function buildForm(FormBuilderInterface $builder) { // TODO: Implement buildForm() method. } + public function getFormDefaultData(): array { return []; @@ -63,7 +69,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac public function getLabels($key, array $values, $data) { if ('export_avg_activity_visit_duration' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period visit duration' : $value; @@ -100,16 +106,20 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac ->select('AVG(activity.travelTime) as export_avg_activity_visit_duration') ->andWhere($qb->expr()->isNotNull('activity.travelTime')); - $qb - ->andWhere( - $qb->expr()->exists( - 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part - JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + if ($this->filterStatsByCenters) { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part + JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) ' + ) ) - ) - ->setParameter('authorized_centers', $centers); + ->setParameter('authorized_centers', $centers); + } + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php index d473a925a..6497a232a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Export\LinkedToACP; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -23,22 +24,27 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; class CountActivity implements ExportInterface, GroupedExportInterface { protected EntityRepository $repository; + private readonly bool $filterStatsByCenters; + public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, + ParameterBagInterface $parameterBag, ) { $this->repository = $em->getRepository(Activity::class); + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -62,7 +68,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { if ('export_count_activity' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } return static fn ($value) => '_header' === $value ? 'Number of activities linked to an accompanying period' : $value; @@ -96,16 +102,20 @@ class CountActivity implements ExportInterface, GroupedExportInterface ->createQueryBuilder('activity') ->join('activity.accompanyingPeriod', 'acp'); - $qb - ->andWhere( - $qb->expr()->exists( - 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part - JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + if ($this->filterStatsByCenters) { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part + JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) ' + ) ) - ) - ->setParameter('authorized_centers', $centers); + ->setParameter('authorized_centers', $centers); + } + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); $qb->select('COUNT(DISTINCT activity.id) as export_count_activity'); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php new file mode 100644 index 000000000..fc22c8edd --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php @@ -0,0 +1,149 @@ +repository = $em->getRepository(Activity::class); + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getFormDefaultData(): array + { + return []; + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'export.export.count_household_on_activity.description'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_count_activity' !== $key) { + throw new \LogicException("the key {$key} is not used by this export"); + } + + return static fn ($value) => '_header' === $value ? 'export.export.count_household_on_activity.header' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_count_activity']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'export.export.count_household_on_activity.title'; + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->repository + ->createQueryBuilder('activity') + ->join('activity.persons', 'person') + ->join('activity.accompanyingPeriod', 'acp') + ->join( + HouseholdMember::class, + 'householdmember', + Query\Expr\Join::WITH, + 'person.id = IDENTITY(householdmember.person) AND householdmember.startDate <= activity.date AND (householdmember.endDate IS NULL OR householdmember.endDate > activity.date)' + ) + ->join('householdmember.household', 'household'); + + if ($this->filterStatsByCenters) { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part + JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + ->setParameter('authorized_centers', $centers); + } + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); + + $qb->select('COUNT(DISTINCT household.id) as export_count_activity'); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + PersonDeclarations::PERSON_TYPE, + PersonDeclarations::HOUSEHOLD_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php new file mode 100644 index 000000000..26432fc0b --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php @@ -0,0 +1,140 @@ +repository = $em->getRepository(Activity::class); + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getFormDefaultData(): array + { + return []; + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'export.export.count_person_on_activity.description'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_count_activity' !== $key) { + throw new \LogicException("the key {$key} is not used by this export"); + } + + return static fn ($value) => '_header' === $value ? 'export.export.count_person_on_activity.header' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_count_activity']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'export.export.count_person_on_activity.title'; + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->repository + ->createQueryBuilder('activity') + ->join('activity.persons', 'person') + ->join('activity.accompanyingPeriod', 'acp'); + + if ($this->filterStatsByCenters) { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part + JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + ->setParameter('authorized_centers', $centers); + } + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); + + $qb->select('COUNT(DISTINCT person.id) as export_count_activity'); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + PersonDeclarations::PERSON_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php index 9ed6bcda2..fc9a44889 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php @@ -12,38 +12,33 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Export\LinkedToACP; use Chill\ActivityBundle\Entity\Activity; +use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Export\ListActivityHelper; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\TranslatableStringExportLabelHelper; use Chill\MainBundle\Export\ListInterface; -use Chill\PersonBundle\Entity\Person\PersonCenterHistory; +use Chill\PersonBundle\Export\Helper\FilterListAccompanyingPeriodHelperInterface; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\FormBuilderInterface; -class ListActivity implements ListInterface, GroupedExportInterface +final readonly class ListActivity implements ListInterface, GroupedExportInterface { - private EntityManagerInterface $entityManager; - - private ListActivityHelper $helper; - - private TranslatableStringExportLabelHelper $translatableStringExportLabelHelper; - public function __construct( - ListActivityHelper $helper, - EntityManagerInterface $entityManager, - TranslatableStringExportLabelHelper $translatableStringExportLabelHelper + private ListActivityHelper $helper, + private EntityManagerInterface $entityManager, + private TranslatableStringExportLabelHelper $translatableStringExportLabelHelper, + private FilterListAccompanyingPeriodHelperInterface $filterListAccompanyingPeriodHelper, ) { - $this->helper = $helper; - $this->entityManager = $entityManager; - $this->translatableStringExportLabelHelper = $translatableStringExportLabelHelper; } public function buildForm(FormBuilderInterface $builder) { $this->helper->buildForm($builder); } + public function getFormDefaultData(): array { return []; @@ -56,7 +51,7 @@ class ListActivity implements ListInterface, GroupedExportInterface public function getDescription() { - return ListActivityHelper::MSG_KEY . 'List activities linked to an accompanying course'; + return ListActivityHelper::MSG_KEY.'List activities linked to an accompanying course'; } public function getGroup(): string @@ -66,22 +61,17 @@ class ListActivity implements ListInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { - switch ($key) { - case 'acpId': - return static function ($value) { - if ('_header' === $value) { - return ListActivityHelper::MSG_KEY . 'accompanying course id'; - } + return match ($key) { + 'acpId' => static function ($value) { + if ('_header' === $value) { + return ListActivityHelper::MSG_KEY.'accompanying course id'; + } - return $value ?? ''; - }; - - case 'scopesNames': - return $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY . 'course circles'); - - default: - return $this->helper->getLabels($key, $values, $data); - } + return $value ?? ''; + }, + 'scopesNames' => $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY.'course circles'), + default => $this->helper->getLabels($key, $values, $data), + }; } public function getQueryKeys($data) @@ -103,7 +93,7 @@ class ListActivity implements ListInterface, GroupedExportInterface public function getTitle() { - return ListActivityHelper::MSG_KEY . 'List activity linked to a course'; + return ListActivityHelper::MSG_KEY.'List activity linked to a course'; } public function getType() @@ -123,30 +113,26 @@ class ListActivity implements ListInterface, GroupedExportInterface ->join('activity.accompanyingPeriod', 'acp') ->leftJoin('acp.participations', 'acppart') ->leftJoin('acppart.person', 'person') - ->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL') - ->andWhere( - $qb->expr()->exists( - 'SELECT 1 - FROM ' . PersonCenterHistory::class . ' acl_count_person_history - WHERE acl_count_person_history.person = person - AND acl_count_person_history.center IN (:authorized_centers) - ' - ) - ) + ->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL'); + + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + + $qb // some grouping are necessary ->addGroupBy('acp.id') ->addOrderBy('activity.date') - ->addOrderBy('activity.id') - ->setParameter('authorized_centers', $centers); + ->addOrderBy('activity.id'); $this->helper->addSelect($qb); // add select for this step $qb ->addSelect('acp.id AS acpId') - ->addSelect('(SELECT AGGREGATE(acpScope.name) FROM ' . Scope::class . ' acpScope WHERE acpScope MEMBER OF acp.scopes) AS scopesNames') + ->addSelect('(SELECT AGGREGATE(acpScope.name) FROM '.Scope::class.' acpScope WHERE acpScope MEMBER OF acp.scopes) AS scopesNames') ->addGroupBy('scopesNames'); + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); + return $qb; } @@ -160,6 +146,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return array_merge( $this->helper->supportsModifiers(), [ + Declarations::ACTIVITY, \Chill\PersonBundle\Export\Declarations::ACP_TYPE, ] ); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php index 8adb3b77f..159fcf78d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Export\LinkedToACP; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -23,23 +24,27 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; class SumActivityDuration implements ExportInterface, GroupedExportInterface { protected EntityRepository $repository; + private readonly bool $filterStatsByCenters; public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, + ParameterBagInterface $parameterBag, ) { $this->repository = $em->getRepository(Activity::class); + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } public function buildForm(FormBuilderInterface $builder) { // TODO: Implement buildForm() method. } + public function getFormDefaultData(): array { return []; @@ -63,7 +68,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { if ('export_sum_activity_duration' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period duration' : $value; @@ -100,16 +105,20 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface $qb->select('SUM(activity.durationTime) as export_sum_activity_duration') ->andWhere($qb->expr()->isNotNull('activity.durationTime')); - $qb - ->andWhere( - $qb->expr()->exists( - 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part - JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + if ($this->filterStatsByCenters) { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part + JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) ' + ) ) - ) - ->setParameter('authorized_centers', $centers); + ->setParameter('authorized_centers', $centers); + } + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php index cc424e68b..27502e2b0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Export\LinkedToACP; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -23,23 +24,27 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; class SumActivityVisitDuration implements ExportInterface, GroupedExportInterface { protected EntityRepository $repository; + private readonly bool $filterStatsByCenters; public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, + ParameterBagInterface $parameterBag, ) { $this->repository = $em->getRepository(Activity::class); + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } public function buildForm(FormBuilderInterface $builder) { // TODO: Implement buildForm() method. } + public function getFormDefaultData(): array { return []; @@ -63,7 +68,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac public function getLabels($key, array $values, $data) { if ('export_sum_activity_visit_duration' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period visit duration' : $value; @@ -100,16 +105,20 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac $qb->select('SUM(activity.travelTime) as export_sum_activity_visit_duration') ->andWhere($qb->expr()->isNotNull('activity.travelTime')); - $qb - ->andWhere( - $qb->expr()->exists( - 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part - JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + if ($this->filterStatsByCenters) { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part + JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) ' + ) ) - ) - ->setParameter('authorized_centers', $centers); + ->setParameter('authorized_centers', $centers); + } + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php index 6360251b3..7614039b0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php @@ -19,22 +19,24 @@ use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\Query; -use LogicException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; class CountActivity implements ExportInterface, GroupedExportInterface { - protected ActivityRepository $activityRepository; + private readonly bool $filterStatsByCenters; public function __construct( - ActivityRepository $activityRepository + private readonly ActivityRepository $activityRepository, + ParameterBagInterface $parameterBag, ) { - $this->activityRepository = $activityRepository; + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -58,7 +60,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { if ('export_count_activity' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } return static fn ($value) => '_header' === $value ? 'Number of activities linked to a person' : $value; @@ -90,23 +92,25 @@ class CountActivity implements ExportInterface, GroupedExportInterface $qb = $this->activityRepository ->createQueryBuilder('activity') - ->join('activity.person', 'person') - ->join('person.centerHistory', 'centerHistory'); + ->join('activity.person', 'person'); $qb->select('COUNT(activity.id) as export_count_activity'); - $qb - ->where( - $qb->expr()->andX( - $qb->expr()->lte('centerHistory.startDate', 'activity.date'), - $qb->expr()->orX( - $qb->expr()->isNull('centerHistory.endDate'), - $qb->expr()->gt('centerHistory.endDate', 'activity.date') + if ($this->filterStatsByCenters) { + $qb + ->join('person.centerHistory', 'centerHistory') + ->where( + $qb->expr()->andX( + $qb->expr()->lte('centerHistory.startDate', 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull('centerHistory.endDate'), + $qb->expr()->gt('centerHistory.endDate', 'activity.date') + ) ) ) - ) - ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) - ->setParameter('centers', $centers); + ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) + ->setParameter('centers', $centers); + } return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php new file mode 100644 index 000000000..46466d883 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php @@ -0,0 +1,140 @@ +filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getFormDefaultData(): array + { + return []; + } + + public function getAllowedFormattersTypes() + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription() + { + return 'export.export.count_household_on_activity_person.description'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to a person'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_count_activity' !== $key) { + throw new \LogicException("the key {$key} is not used by this export"); + } + + return static fn ($value) => '_header' === $value ? 'export.export.count_household_on_activity_person.header' : $value; + } + + public function getQueryKeys($data) + { + return ['export_count_activity']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle() + { + return 'export.export.count_household_on_activity_person.title'; + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->activityRepository + ->createQueryBuilder('activity') + ->join('activity.person', 'person') + ->join( + HouseholdMember::class, + 'householdmember', + Query\Expr\Join::WITH, + 'person = householdmember.person AND householdmember.startDate <= activity.date AND (householdmember.endDate IS NULL OR householdmember.endDate > activity.date)' + ) + ->join('householdmember.household', 'household'); + + $qb->select('COUNT(DISTINCT household.id) as export_count_activity'); + + if ($this->filterStatsByCenters) { + $qb + ->join('person.centerHistory', 'centerHistory') + ->where( + $qb->expr()->andX( + $qb->expr()->lte('centerHistory.startDate', 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull('centerHistory.endDate'), + $qb->expr()->gt('centerHistory.endDate', 'activity.date') + ) + ) + ) + ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) + ->setParameter('centers', $centers); + } + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::STATS; + } + + public function supportsModifiers() + { + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_PERSON, + PersonDeclarations::PERSON_TYPE, + PersonDeclarations::HOUSEHOLD_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php index d14111ba7..f7b5f3a8a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php @@ -20,24 +20,18 @@ use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\ListInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Export\Declarations as PersonDeclarations; -use DateTime; use Doctrine\DBAL\Exception\InvalidArgumentException; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Contracts\Translation\TranslatorInterface; -use function array_key_exists; -use function count; -use function in_array; - class ListActivity implements ListInterface, GroupedExportInterface { - protected EntityManagerInterface $entityManager; - protected array $fields = [ 'id', 'date', @@ -51,23 +45,16 @@ class ListActivity implements ListInterface, GroupedExportInterface 'person_lastname', 'person_id', ]; - - protected TranslatableStringHelperInterface $translatableStringHelper; - - protected TranslatorInterface $translator; - - private ActivityRepository $activityRepository; + private readonly bool $filterStatsByCenters; public function __construct( - EntityManagerInterface $em, - TranslatorInterface $translator, - TranslatableStringHelperInterface $translatableStringHelper, - ActivityRepository $activityRepository + protected EntityManagerInterface $entityManager, + protected TranslatorInterface $translator, + protected TranslatableStringHelperInterface $translatableStringHelper, + private readonly ActivityRepository $activityRepository, + ParameterBagInterface $parameterBag, ) { - $this->entityManager = $em; - $this->translator = $translator; - $this->translatableStringHelper = $translatableStringHelper; - $this->activityRepository = $activityRepository; + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } public function buildForm(FormBuilderInterface $builder) @@ -79,7 +66,7 @@ class ListActivity implements ListInterface, GroupedExportInterface 'label' => 'Fields to include in export', 'constraints' => [new Callback([ 'callback' => static function ($selected, ExecutionContextInterface $context) { - if (count($selected) === 0) { + if (0 === \count($selected)) { $context->buildViolation('You must select at least one element') ->atPath('fields') ->addViolation(); @@ -88,6 +75,7 @@ class ListActivity implements ListInterface, GroupedExportInterface ])], ]); } + public function getFormDefaultData(): array { return []; @@ -117,7 +105,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return 'date'; } - $date = DateTime::createFromFormat('Y-m-d H:i:s', $value); + $date = \DateTime::createFromFormat('Y-m-d H:i:s', $value); return $date->format('d-m-Y'); }; @@ -141,11 +129,11 @@ class ListActivity implements ListInterface, GroupedExportInterface $activity = $activityRepository->find($value); - return implode(', ', array_map(fn (ActivityReason $r) => '"' . + return implode(', ', array_map(fn (ActivityReason $r) => '"'. $this->translatableStringHelper->localize($r->getCategory()->getName()) - . ' > ' . + .' > '. $this->translatableStringHelper->localize($r->getName()) - . '"', $activity->getReasons()->toArray())); + .'"', $activity->getReasons()->toArray())); }; case 'circle_name': @@ -154,7 +142,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return 'circle'; } - return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR)); + return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)); }; case 'type_name': @@ -163,7 +151,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return 'activity type'; } - return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR)); + return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)); }; default: @@ -202,7 +190,7 @@ class ListActivity implements ListInterface, GroupedExportInterface $centers = array_map(static fn ($el) => $el['center'], $acl); // throw an error if any fields are present - if (!array_key_exists('fields', $data)) { + if (!\array_key_exists('fields', $data)) { throw new InvalidArgumentException('Any fields have been checked.'); } @@ -210,23 +198,25 @@ class ListActivity implements ListInterface, GroupedExportInterface $qb ->from('ChillActivityBundle:Activity', 'activity') - ->join('activity.person', 'actperson') - ->join('actperson.centerHistory', 'centerHistory'); + ->join('activity.person', 'actperson'); - $qb->where( - $qb->expr()->andX( - $qb->expr()->lte('centerHistory.startDate', 'activity.date'), - $qb->expr()->orX( - $qb->expr()->isNull('centerHistory.endDate'), - $qb->expr()->gt('centerHistory.endDate', 'activity.date') + if ($this->filterStatsByCenters) { + $qb->join('actperson.centerHistory', 'centerHistory'); + $qb->where( + $qb->expr()->andX( + $qb->expr()->lte('centerHistory.startDate', 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull('centerHistory.endDate'), + $qb->expr()->gt('centerHistory.endDate', 'activity.date') + ) ) ) - ) - ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) - ->setParameter('centers', $centers); + ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) + ->setParameter('centers', $centers); + } foreach ($this->fields as $f) { - if (in_array($f, $data['fields'], true)) { + if (\in_array($f, $data['fields'], true)) { switch ($f) { case 'id': $qb->addSelect('activity.id AS id'); @@ -299,7 +289,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return [ Declarations::ACTIVITY, Declarations::ACTIVITY_PERSON, - //PersonDeclarations::PERSON_TYPE, + PersonDeclarations::PERSON_TYPE, ]; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php index e68d47cd3..491d1c094 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php @@ -20,7 +20,7 @@ use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\Query; -use LogicException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; /** @@ -30,29 +30,27 @@ use Symfony\Component\Form\FormBuilderInterface; */ class StatActivityDuration implements ExportInterface, GroupedExportInterface { - public const SUM = 'sum'; - - /** - * The action for this report. - */ - protected string $action; - - private ActivityRepository $activityRepository; + final public const SUM = 'sum'; + private readonly bool $filterStatsByCenters; /** * @param string $action the stat to perform */ public function __construct( - ActivityRepository $activityRepository, - string $action = 'sum' + private readonly ActivityRepository $activityRepository, + ParameterBagInterface $parameterBag, + /** + * The action for this report. + */ + protected string $action = 'sum' ) { - $this->action = $action; - $this->activityRepository = $activityRepository; + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -69,7 +67,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface return 'Sum activities linked to a person duration by various parameters.'; } - throw new LogicException('this action is not supported: ' . $this->action); + throw new \LogicException('this action is not supported: '.$this->action); } public function getGroup(): string @@ -80,7 +78,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { if ('export_stat_activity' !== $key) { - throw new LogicException(sprintf('The key %s is not used by this export', $key)); + throw new \LogicException(sprintf('The key %s is not used by this export', $key)); } $header = self::SUM === $this->action ? 'Sum activities linked to a person duration' : false; @@ -104,7 +102,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface return 'Sum activity linked to a person duration'; } - throw new LogicException('This action is not supported: ' . $this->action); + throw new \LogicException('This action is not supported: '.$this->action); } public function getType(): string @@ -128,21 +126,23 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface } $qb->select($select) - ->join('activity.person', 'person') - ->join('person.centerHistory', 'centerHistory'); + ->join('activity.person', 'person'); - $qb - ->where( - $qb->expr()->andX( - $qb->expr()->lte('centerHistory.startDate', 'activity.date'), - $qb->expr()->orX( - $qb->expr()->isNull('centerHistory.endDate'), - $qb->expr()->gt('centerHistory.endDate', 'activity.date') + if ($this->filterStatsByCenters) { + $qb + ->join('person.centerHistory', 'centerHistory') + ->where( + $qb->expr()->andX( + $qb->expr()->lte('centerHistory.startDate', 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull('centerHistory.endDate'), + $qb->expr()->gt('centerHistory.endDate', 'activity.date') + ) ) ) - ) - ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) - ->setParameter('centers', $centers); + ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) + ->setParameter('centers', $centers); + } return $qb; } @@ -157,7 +157,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface return [ Declarations::ACTIVITY, Declarations::ACTIVITY_PERSON, - //PersonDeclarations::PERSON_TYPE, + PersonDeclarations::PERSON_TYPE, ]; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php index fae6ea6a6..73b49e082 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php @@ -23,53 +23,24 @@ use Chill\PersonBundle\Export\Helper\LabelPersonHelper; use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\QueryBuilder; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Contracts\Translation\TranslatorInterface; -use const SORT_NUMERIC; class ListActivityHelper { - public const MSG_KEY = 'export.list.activity.'; - - private ActivityPresenceRepositoryInterface $activityPresenceRepository; - - private ActivityTypeRepositoryInterface $activityTypeRepository; - - private DateTimeHelper $dateTimeHelper; - - private LabelPersonHelper $labelPersonHelper; - - private LabelThirdPartyHelper $labelThirdPartyHelper; - - private TranslatableStringHelperInterface $translatableStringHelper; - - private TranslatableStringExportLabelHelper $translatableStringLabelHelper; - - private TranslatorInterface $translator; - - private UserHelper $userHelper; + final public const MSG_KEY = 'export.list.activity.'; public function __construct( - ActivityPresenceRepositoryInterface $activityPresenceRepository, - ActivityTypeRepositoryInterface $activityTypeRepository, - DateTimeHelper $dateTimeHelper, - LabelPersonHelper $labelPersonHelper, - LabelThirdPartyHelper $labelThirdPartyHelper, - TranslatorInterface $translator, - TranslatableStringHelperInterface $translatableStringHelper, - TranslatableStringExportLabelHelper $translatableStringLabelHelper, - UserHelper $userHelper + private readonly ActivityPresenceRepositoryInterface $activityPresenceRepository, + private readonly ActivityTypeRepositoryInterface $activityTypeRepository, + private readonly DateTimeHelper $dateTimeHelper, + private readonly LabelPersonHelper $labelPersonHelper, + private readonly LabelThirdPartyHelper $labelThirdPartyHelper, + private readonly TranslatorInterface $translator, + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly TranslatableStringExportLabelHelper $translatableStringLabelHelper, + private readonly UserHelper $userHelper ) { - $this->activityPresenceRepository = $activityPresenceRepository; - $this->activityTypeRepository = $activityTypeRepository; - $this->dateTimeHelper = $dateTimeHelper; - $this->labelPersonHelper = $labelPersonHelper; - $this->labelThirdPartyHelper = $labelThirdPartyHelper; - $this->translator = $translator; - $this->translatableStringHelper = $translatableStringHelper; - $this->translatableStringLabelHelper = $translatableStringLabelHelper; - $this->userHelper = $userHelper; } public function addSelect(QueryBuilder $qb): void @@ -85,7 +56,7 @@ class ListActivityHelper ->addSelect('AGGREGATE(actPerson.id) AS personsNames') ->leftJoin('activity.users', 'users_u') ->addSelect('AGGREGATE(users_u.id) AS usersIds') - ->addSelect('AGGREGATE(users_u.id) AS usersNames') + ->addSelect('AGGREGATE(JSON_BUILD_OBJECT(\'uid\', users_u.id, \'d\', activity.date)) AS usersNames') ->leftJoin('activity.thirdParties', 'thirdparty') ->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesIds') ->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesNames') @@ -96,9 +67,9 @@ class ListActivityHelper ->leftJoin('activity.location', 'location') ->addSelect('location.name AS locationName') ->addSelect('activity.sentReceived') - ->addSelect('IDENTITY(activity.createdBy) AS createdBy') + ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.createdBy), \'d\', activity.createdAt) AS createdBy') ->addSelect('activity.createdAt') - ->addSelect('IDENTITY(activity.updatedBy) AS updatedBy') + ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.updatedBy), \'d\', activity.updatedAt) AS updatedBy') ->addSelect('activity.updatedAt') ->addGroupBy('activity.id') ->addGroupBy('location.id'); @@ -115,113 +86,78 @@ class ListActivityHelper public function getLabels($key, array $values, $data) { - switch ($key) { - case 'createdAt': - case 'updatedAt': - return $this->dateTimeHelper->getLabel($key); + return match ($key) { + 'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel($key), + 'createdBy', 'updatedBy' => $this->userHelper->getLabel($key, $values, $key), + 'date' => $this->dateTimeHelper->getLabel(self::MSG_KEY.$key), + 'attendeeName' => function ($value) { + if ('_header' === $value) { + return 'Attendee'; + } - case 'createdBy': - case 'updatedBy': - return $this->userHelper->getLabel($key, $values, $key); + if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) { + return ''; + } - case 'date': - return $this->dateTimeHelper->getLabel(self::MSG_KEY . $key); + return $this->translatableStringHelper->localize($presence->getName()); + }, + 'listReasons' => $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons'), + 'typeName' => function ($value) { + if ('_header' === $value) { + return 'Activity type'; + } - case 'attendeeName': - return function ($value) { - if ('_header' === $value) { - return 'Attendee'; - } + if (null === $value || null === $type = $this->activityTypeRepository->find($value)) { + return ''; + } - if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) { - return ''; - } + return $this->translatableStringHelper->localize($type->getName()); + }, + 'usersNames' => $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY.'users name'), + 'usersIds', 'thirdPartiesIds', 'personsIds' => static function ($value) use ($key) { + if ('_header' === $value) { + return match ($key) { + 'usersIds' => self::MSG_KEY.'users ids', + 'thirdPartiesIds' => self::MSG_KEY.'third parties ids', + 'personsIds' => self::MSG_KEY.'persons ids', + }; + } - return $this->translatableStringHelper->localize($presence->getName()); - }; + $decoded = json_decode((string) $value, null, 512, JSON_THROW_ON_ERROR); - case 'listReasons': - return $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons'); + return implode( + '|', + array_unique( + array_filter($decoded, static fn (?int $id) => null !== $id), + \SORT_NUMERIC + ) + ); + }, + 'personsNames' => $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY.'persons name'), + 'thirdPartiesNames' => $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY.'thirds parties'), + 'sentReceived' => function ($value) { + if ('_header' === $value) { + return self::MSG_KEY.'sent received'; + } - case 'typeName': - return function ($value) { - if ('_header' === $value) { - return 'Activity type'; - } + if (null === $value) { + return ''; + } - if (null === $value || null === $type = $this->activityTypeRepository->find($value)) { - return ''; - } + return $this->translator->trans($value); + }, + default => function ($value) use ($key) { + if ('_header' === $value) { + return self::MSG_KEY.$key; + } - return $this->translatableStringHelper->localize($type->getName()); - }; + if (null === $value) { + return ''; + } - case 'usersNames': - return $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY . 'users name'); - - case 'usersIds': - case 'thirdPartiesIds': - case 'personsIds': - return static function ($value) use ($key) { - if ('_header' === $value) { - switch ($key) { - case 'usersIds': - return self::MSG_KEY . 'users ids'; - - case 'thirdPartiesIds': - return self::MSG_KEY . 'third parties ids'; - - case 'personsIds': - return self::MSG_KEY . 'persons ids'; - - default: - throw new LogicException('key not supported'); - } - } - - $decoded = json_decode($value, null, 512, JSON_THROW_ON_ERROR); - - return implode( - '|', - array_unique( - array_filter($decoded, static fn (?int $id) => null !== $id), - SORT_NUMERIC - ) - ); - }; - - case 'personsNames': - return $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY . 'persons name'); - - case 'thirdPartiesNames': - return $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY . 'thirds parties'); - - case 'sentReceived': - return function ($value) { - if ('_header' === $value) { - return self::MSG_KEY . 'sent received'; - } - - if (null === $value) { - return ''; - } - - return $this->translator->trans($value); - }; - - default: - return function ($value) use ($key) { - if ('_header' === $value) { - return self::MSG_KEY . $key; - } - - if (null === $value) { - return ''; - } - - return $this->translator->trans($value); - }; - } + return $this->translator->trans($value); + }, + }; } public function getQueryKeys($data) diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php index 4ffc3b38c..89fe2e618 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php @@ -15,24 +15,23 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Form\Type\PickRollingDateType; +use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class ActivityTypeFilter implements FilterInterface +final readonly class ActivityTypeFilter implements FilterInterface { - private ActivityTypeRepositoryInterface $activityTypeRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; + private const BASE_EXISTS = 'SELECT 1 FROM '.Activity::class.' act_type_filter_activity WHERE act_type_filter_activity.accompanyingPeriod = acp'; public function __construct( - ActivityTypeRepositoryInterface $activityTypeRepository, - TranslatableStringHelperInterface $translatableStringHelper + private ActivityTypeRepositoryInterface $activityTypeRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private RollingDateConverterInterface $rollingDateConverter, ) { - $this->activityTypeRepository = $activityTypeRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -42,13 +41,26 @@ class ActivityTypeFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - $qb->andWhere( - $qb->expr()->exists( - 'SELECT 1 FROM ' . Activity::class . ' act_type_filter_activity - WHERE act_type_filter_activity.activityType IN (:act_type_filter_activity_types) AND act_type_filter_activity.accompanyingPeriod = acp' - ) - ); - $qb->setParameter('act_type_filter_activity_types', $data['accepted_activitytypes']); + $exists = self::BASE_EXISTS; + + if (count($data['accepted_activitytypes']) > 0) { + $exists .= ' AND act_type_filter_activity.activityType IN (:act_type_filter_activity_types)'; + $qb->setParameter('act_type_filter_activity_types', $data['accepted_activitytypes']); + } + + if (null !== $data['date_after']) { + $exists .= ' AND act_type_filter_activity.date >= :act_type_filter_activity_date_after'; + $qb->setParameter('act_type_filter_activity_date_after', $this->rollingDateConverter->convert($data['date_after'])); + } + + if (null !== $data['date_before']) { + $exists .= ' AND act_type_filter_activity.date >= :act_type_filter_activity_date_before'; + $qb->setParameter('act_type_filter_activity_date_before', $this->rollingDateConverter->convert($data['date_before'])); + } + + if (self::BASE_EXISTS !== $exists) { + $qb->andWhere($qb->expr()->exists($exists)); + } } public function applyOn() @@ -61,16 +73,33 @@ class ActivityTypeFilter implements FilterInterface $builder->add('accepted_activitytypes', EntityType::class, [ 'class' => ActivityType::class, 'choices' => $this->activityTypeRepository->findAllActive(), - 'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()) . ' > ' : '') + 'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()).' > ' : '') . $this->translatableStringHelper->localize($aty->getName()), 'multiple' => true, 'expanded' => true, ]); + + $builder->add('date_after', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.acp_by_activity_type.activity after', + 'help' => 'export.filter.activity.acp_by_activity_type.activity after help', + 'required' => false, + ]); + + $builder->add('date_before', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.acp_by_activity_type.activity before', + 'help' => 'export.filter.activity.acp_by_activity_type.activity before help', + 'required' => false, + ]); } + public function getFormDefaultData(): array { - return []; + return [ + 'accepted_activitytypes' => [], + 'date_after' => null, + 'date_before' => null, + ]; } public function describeAction($data, $format = 'string'): array @@ -81,8 +110,12 @@ class ActivityTypeFilter implements FilterInterface $types[] = $this->translatableStringHelper->localize($aty->getName()); } - return ['Filtered by activity types: only %activitytypes%', [ - '%activitytypes%' => implode(', ', $types), + return ['export.filter.activity.acp_by_activity_type.acp_containing_at_least_one_activitytypes', [ + 'activitytypes' => implode(', ', $types), + 'has_date_after' => null !== $data['date_after'] ? 1 : 0, + 'date_after' => $this->rollingDateConverter->convert($data['date_after']), + 'has_date_before' => null !== $data['date_before'] ? 1 : 0, + 'date_before' => $this->rollingDateConverter->convert($data['date_before']), ]]; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php index 08f6bbbc3..3eb4e130e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php @@ -18,15 +18,11 @@ use Chill\PersonBundle\Form\Type\PickSocialActionType; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class BySocialActionFilter implements FilterInterface { - private SocialActionRender $actionRender; - - public function __construct(SocialActionRender $actionRender) + public function __construct(private readonly SocialActionRender $actionRender) { - $this->actionRender = $actionRender; } public function addRole(): ?string @@ -36,7 +32,7 @@ class BySocialActionFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actsocialaction', $qb->getAllAliases(), true)) { + if (!\in_array('actsocialaction', $qb->getAllAliases(), true)) { $qb->join('activity.socialActions', 'actsocialaction'); } @@ -60,6 +56,7 @@ class BySocialActionFilter implements FilterInterface 'multiple' => true, ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php index bd6e2ef4a..b482ac156 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php @@ -18,15 +18,11 @@ use Chill\PersonBundle\Form\Type\PickSocialIssueType; use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class BySocialIssueFilter implements FilterInterface { - private SocialIssueRender $issueRender; - - public function __construct(SocialIssueRender $issueRender) + public function __construct(private readonly SocialIssueRender $issueRender) { - $this->issueRender = $issueRender; } public function addRole(): ?string @@ -36,7 +32,7 @@ class BySocialIssueFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actsocialissue', $qb->getAllAliases(), true)) { + if (!\in_array('actsocialissue', $qb->getAllAliases(), true)) { $qb->join('activity.socialIssues', 'actsocialissue'); } @@ -60,6 +56,7 @@ class BySocialIssueFilter implements FilterInterface 'multiple' => true, ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php index b5dab4009..afd708d33 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php @@ -17,6 +17,9 @@ use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; +/** + * Filter accompanying periods to keep only the one without any activity. + */ class HasNoActivityFilter implements FilterInterface { public function addRole(): ?string @@ -29,7 +32,7 @@ class HasNoActivityFilter implements FilterInterface $qb ->andWhere(' NOT EXISTS ( - SELECT 1 FROM ' . Activity::class . ' activity + SELECT 1 FROM '.Activity::class.' activity WHERE activity.accompanyingPeriod = acp ) '); @@ -42,8 +45,9 @@ class HasNoActivityFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { - //no form needed + // no form needed } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php new file mode 100644 index 000000000..77efa9a8b --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php @@ -0,0 +1,90 @@ +add('start_date', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.course_having_activity_between_date.Receiving an activity after', + ]) + ->add('end_date', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.course_having_activity_between_date.Receiving an activity before', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), + 'end_date' => new RollingDate(RollingDate::T_TODAY), + ]; + } + + public function describeAction($data, $format = 'string') + { + return [ + 'export.filter.activity.course_having_activity_between_date.Only course having an activity between from and to', + [ + 'from' => $this->rollingDateConverter->convert($data['start_date']), + 'to' => $this->rollingDateConverter->convert($data['end_date']), + ], + ]; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $alias = 'act_period_having_act_betw_date_alias'; + $from = 'act_period_having_act_betw_date_start'; + $to = 'act_period_having_act_betw_date_end'; + + $qb->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.Activity::class." {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = acp" + ) + ); + + $qb + ->setParameter($from, $this->rollingDateConverter->convert($data['start_date'])) + ->setParameter($to, $this->rollingDateConverter->convert($data['end_date'])); + } + + public function applyOn() + { + return \Chill\PersonBundle\Export\Declarations::ACP_TYPE; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php deleted file mode 100644 index adb1e94f1..000000000 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php +++ /dev/null @@ -1,98 +0,0 @@ -translatableStringHelper = $translatableStringHelper; - } - - public function addRole(): ?string - { - return null; - } - - public function alterQuery(QueryBuilder $qb, $data) - { - if (!in_array('actuser', $qb->getAllAliases(), true)) { - $qb->join('activity.user', 'actuser'); - } - - $where = $qb->getDQLPart('where'); - - $clause = $qb->expr()->in('actuser.mainScope', ':userscope'); - - if ($where instanceof Andx) { - $where->add($clause); - } else { - $where = $qb->expr()->andX($clause); - } - - $qb->add('where', $where); - $qb->setParameter('userscope', $data['accepted_userscope']); - } - - public function applyOn(): string - { - return Declarations::ACTIVITY_ACP; - } - - public function buildForm(FormBuilderInterface $builder) - { - $builder->add('accepted_userscope', EntityType::class, [ - 'class' => Scope::class, - 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( - $s->getName() - ), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; - } - - public function describeAction($data, $format = 'string'): array - { - $scopes = []; - - foreach ($data['accepted_userscope'] as $s) { - $scopes[] = $this->translatableStringHelper->localize( - $s->getName() - ); - } - - return ['Filtered activity by userscope: only %scopes%', [ - '%scopes%' => implode(', ', $scopes), - ]]; - } - - public function getTitle(): string - { - return 'Filter activity by userscope'; - } -} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php index e582acc12..93e70076d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php @@ -13,30 +13,18 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\Export\FilterType; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormError; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; use Symfony\Contracts\Translation\TranslatorInterface; class ActivityDateFilter implements FilterInterface { - protected TranslatorInterface $translator; - - private RollingDateConverterInterface $rollingDateConverter; - - public function __construct( - TranslatorInterface $translator, - RollingDateConverterInterface $rollingDateConverter - ) { - $this->translator = $translator; - $this->rollingDateConverter = $rollingDateConverter; + public function __construct(protected TranslatorInterface $translator, private readonly RollingDateConverterInterface $rollingDateConverter) + { } public function addRole(): ?string @@ -84,47 +72,8 @@ class ActivityDateFilter implements FilterInterface ->add('date_to', PickRollingDateType::class, [ 'label' => 'Activities before this date', ]); - - $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { - /** @var \Symfony\Component\Form\FormInterface $filterForm */ - $filterForm = $event->getForm()->getParent(); - $enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData(); - - if (true === $enabled) { - // if the filter is enabled, add some validation - $form = $event->getForm(); - $date_from = $form->get('date_from')->getData(); - $date_to = $form->get('date_to')->getData(); - - // check that fields are not empty - if (null === $date_from) { - $form->get('date_from')->addError(new FormError( - $this->translator->trans('This field ' - . 'should not be empty') - )); - } - - if (null === $date_to) { - $form->get('date_to')->addError(new FormError( - $this->translator->trans('This field ' - . 'should not be empty') - )); - } - - // check that date_from is before date_to - if ( - (null !== $date_from && null !== $date_to) - && $date_from >= $date_to - ) { - $form->get('date_to')->addError(new FormError( - $this->translator->trans('This date should be after ' - . 'the date given in "Implied in an activity after ' - . 'this date" field') - )); - } - } - }); } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php new file mode 100644 index 000000000..f1f1a668c --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php @@ -0,0 +1,83 @@ +add('presences', EntityType::class, [ + 'class' => ActivityPresence::class, + 'choice_label' => fn (ActivityPresence $presence) => $this->translatableStringHelper->localize($presence->getName()) + .($presence->isActive() ? '' : ' ('.$this->translator->trans('inactive').')'), + 'multiple' => true, + 'expanded' => true, + 'label' => 'export.filter.activity.by_presence.presences', + ]); + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, $format = 'string') + { + $presences = array_map( + fn (ActivityPresence $presence) => $this->translatableStringHelper->localize($presence->getName()), + $data['presences'] instanceof Collection ? $data['presences']->toArray() : $data['presences'] + ); + + return [ + 'export.filter.activity.by_presence.Filtered by activity presence: only %presences%', + ['%presences%' => implode(', ', $presences)], + ]; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + ->andWhere('activity.attendee IN (:activity_presence_filter_presences)') + ->setParameter('activity_presence_filter_presences', $data['presences']); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php index 8dfcb543c..b59dfa301 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php @@ -22,20 +22,12 @@ use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; -use function count; - class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInterface { - protected ActivityTypeRepositoryInterface $activityTypeRepository; - - protected TranslatableStringHelperInterface $translatableStringHelper; - public function __construct( - TranslatableStringHelperInterface $translatableStringHelper, - ActivityTypeRepositoryInterface $activityTypeRepository + protected TranslatableStringHelperInterface $translatableStringHelper, + protected ActivityTypeRepositoryInterface $activityTypeRepository ) { - $this->translatableStringHelper = $translatableStringHelper; - $this->activityTypeRepository = $activityTypeRepository; } public function addRole(): ?string @@ -61,7 +53,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter $builder->add('types', EntityType::class, [ 'choices' => $this->activityTypeRepository->findAllActive(), 'class' => ActivityType::class, - 'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()) . ' > ' : '') + 'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()).' > ' : '') . $this->translatableStringHelper->localize($aty->getName()), 'group_by' => function (ActivityType $type) { @@ -78,6 +70,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter ], ]); } + public function getFormDefaultData(): array { return []; @@ -88,7 +81,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter // collect all the reasons'name used in this filter in one array $reasonsNames = array_map( fn (ActivityType $t): string => $this->translatableStringHelper->localize($t->getName()), - $this->activityTypeRepository->findBy(['id' => $data['types']->toArray()]) + $this->activityTypeRepository->findBy(['id' => $data['types'] instanceof \Doctrine\Common\Collections\Collection ? $data['types']->toArray() : $data['types']]) ); return ['Filtered by activity type: only %list%', [ @@ -103,7 +96,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter public function validateForm($data, ExecutionContextInterface $context) { - if (null === $data['types'] || count($data['types']) === 0) { + if (null === $data['types'] || 0 === \count($data['types'])) { $context ->buildViolation('At least one type must be chosen') ->addViolation(); diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php index a63ca2629..313ee7cbc 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php @@ -20,11 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface; class ActivityUsersFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) + public function __construct(private readonly UserRender $userRender) { - $this->userRender = $userRender; } public function addRole(): ?string @@ -37,8 +34,8 @@ class ActivityUsersFilter implements FilterInterface $orX = $qb->expr()->orX(); foreach ($data['accepted_users'] as $key => $user) { - $orX->add($qb->expr()->isMemberOf(':activity_users_filter_u' . $key, 'activity.users')); - $qb->setParameter('activity_users_filter_u' . $key, $user); + $orX->add($qb->expr()->isMemberOf(':activity_users_filter_u'.$key, 'activity.users')); + $qb->setParameter('activity_users_filter_u'.$key, $user); } $qb->andWhere($orX); @@ -56,6 +53,7 @@ class ActivityUsersFilter implements FilterInterface 'label' => 'Users', ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php similarity index 87% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php index add9c7c3d..8cb7db569 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\FilterInterface; @@ -20,11 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface; class ByCreatorFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) + public function __construct(private readonly UserRender $userRender) { - $this->userRender = $userRender; } public function addRole(): ?string @@ -43,7 +40,7 @@ class ByCreatorFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -52,6 +49,7 @@ class ByCreatorFilter implements FilterInterface 'multiple' => true, ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php new file mode 100644 index 000000000..c43edb666 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php @@ -0,0 +1,117 @@ +leftJoin('activity.createdBy', "{$p}_user") + ->leftJoin( + UserJobHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // job_at based on activity.date + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'activity.date') + ) + ) + ) + ->andWhere( + $qb->expr()->in("{$p}_history.job", ":{$p}_jobs") + ) + ->setParameter( + "{$p}_jobs", + $data['jobs'], + ); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('jobs', EntityType::class, [ + 'choices' => $this->userJobRepository->findAllActive(), + 'class' => UserJob::class, + 'choice_label' => fn (UserJob $s) => $this->translatableStringHelper->localize( + $s->getLabel() + ).($s->isActive() ? '' : '('.$this->translator->trans('inactive').')'), + 'label' => 'export.filter.activity.by_creator_job.job_form_label', + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $jobs = array_map( + fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), + $data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs'] + ); + + return ['export.filter.activity.by_creator_job.Filtered activity by user job: only %jobs%', [ + '%jobs%' => implode(', ', $jobs), + ]]; + } + + public function getFormDefaultData(): array + { + return [ + 'jobs' => [], + ]; + } + + public function getTitle(): string + { + return 'export.filter.activity.by_creator_job.Filter activity by user job'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php new file mode 100644 index 000000000..dad670775 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php @@ -0,0 +1,116 @@ +leftJoin('activity.createdBy', "{$p}_user") + ->leftJoin( + UserScopeHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // scope_at based on activity.date + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'activity.date') + ) + ) + ) + ->andWhere( + $qb->expr()->in("{$p}_history.scope", ":{$p}_scopes") + ) + ->setParameter( + "{$p}_scopes", + $data['scopes'], + ); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('scopes', EntityType::class, [ + 'class' => Scope::class, + 'choices' => $this->scopeRepository->findAllActive(), + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( + $s->getName() + ), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $scopes = []; + + foreach ($data['scopes'] as $s) { + $scopes[] = $this->translatableStringHelper->localize( + $s->getName() + ); + } + + return ['export.filter.activity.by_creator_scope.Filtered activity by user scope: only %scopes%', [ + '%scopes%' => implode(', ', $scopes), + ]]; + } + + public function getFormDefaultData(): array + { + return [ + 'scopes' => [], + ]; + } + + public function getTitle(): string + { + return 'export.filter.activity.by_creator_scope.Filter activity by user scope'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php similarity index 84% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php index 807ba3903..eca62b79e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\FilterInterface; @@ -22,17 +22,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; class EmergencyFilter implements FilterInterface { private const CHOICES = [ - 'activity is emergency' => true, - 'activity is not emergency' => false, + 'activity is emergency' => 'true', + 'activity is not emergency' => 'false', ]; - private const DEFAULT_CHOICE = false; + private const DEFAULT_CHOICE = 'false'; - private TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) + public function __construct(private readonly TranslatorInterface $translator) { - $this->translator = $translator; } public function addRole(): ?string @@ -58,7 +55,7 @@ class EmergencyFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -70,6 +67,7 @@ class EmergencyFilter implements FilterInterface 'empty_data' => self::DEFAULT_CHOICE, ]); } + public function getFormDefaultData(): array { return ['accepted_emergency' => self::DEFAULT_CHOICE]; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php similarity index 91% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php index 312f3a6a0..77b4ce20d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php @@ -9,12 +9,11 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickUserLocationType; -use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -36,7 +35,7 @@ class LocationFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -46,6 +45,7 @@ class LocationFilter implements FilterInterface 'label' => 'pick location', ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php similarity index 82% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php index 1c3415460..144e79913 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\FilterInterface; @@ -18,15 +18,11 @@ use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class LocationTypeFilter implements FilterInterface { - private TranslatableStringHelper $translatableStringHelper; - - public function __construct(TranslatableStringHelper $translatableStringHelper) + public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) { - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -36,7 +32,7 @@ class LocationTypeFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actloc', $qb->getAllAliases(), true)) { + if (!\in_array('actloc', $qb->getAllAliases(), true)) { $qb->join('activity.location', 'actloc'); } @@ -55,16 +51,17 @@ class LocationTypeFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { $builder->add('accepted_locationtype', PickLocationTypeType::class, [ 'multiple' => true, - //'label' => false, + // 'label' => false, ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php index 31cfde6b6..5e2ee5cbe 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php @@ -17,28 +17,17 @@ use Chill\ActivityBundle\Repository\ActivityReasonRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; -use function count; -use function in_array; - class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInterface { - protected ActivityReasonRepository $activityReasonRepository; - - protected TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct( - TranslatableStringHelper $helper, - ActivityReasonRepository $activityReasonRepository - ) { - $this->translatableStringHelper = $helper; - $this->activityReasonRepository = $activityReasonRepository; + public function __construct(protected TranslatableStringHelper $translatableStringHelper, protected ActivityReasonRepository $activityReasonRepository) + { } public function addRole(): ?string @@ -52,7 +41,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt $join = $qb->getDQLPart('join'); $clause = $qb->expr()->in('actreasons', ':selected_activity_reasons'); - if (!in_array('actreasons', $qb->getAllAliases(), true)) { + if (!\in_array('actreasons', $qb->getAllAliases(), true)) { $qb->join('activity.reasons', 'actreasons'); } @@ -82,6 +71,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt 'expanded' => false, ]); } + public function getFormDefaultData(): array { return []; @@ -91,8 +81,8 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt { // collect all the reasons'name used in this filter in one array $reasonsNames = array_map( - fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"', - $this->activityReasonRepository->findBy(['id' => $data['reasons']->toArray()]) + fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"', + $this->activityReasonRepository->findBy(['id' => $data['reasons'] instanceof Collection ? $data['reasons']->toArray() : $data['reasons']]) ); return [ @@ -110,7 +100,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt public function validateForm($data, ExecutionContextInterface $context) { - if (null === $data['reasons'] || count($data['reasons']) === 0) { + if (null === $data['reasons'] || 0 === \count($data['reasons'])) { $context ->buildViolation('At least one reason must be chosen') ->addViolation(); diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php index 490b5fd0c..c424bec31 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php @@ -16,41 +16,23 @@ use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Repository\ActivityReasonRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\Export\FilterType; +use Chill\MainBundle\Form\Type\PickRollingDateType; +use Chill\MainBundle\Service\RollingDate\RollingDate; +use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Export\Declarations; -use DateTime; -use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormError; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; -use Symfony\Component\Form\FormInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Symfony\Contracts\Translation\TranslatorInterface; -use function count; - -class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface +final readonly class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface { - protected ActivityReasonRepository $activityReasonRepository; - - protected TranslatableStringHelperInterface $translatableStringHelper; - - protected TranslatorInterface $translator; - public function __construct( - TranslatableStringHelper $translatableStringHelper, - ActivityReasonRepository $activityReasonRepository, - TranslatorInterface $translator + private TranslatableStringHelper $translatableStringHelper, + private ActivityReasonRepository $activityReasonRepository, + private RollingDateConverterInterface $rollingDateConverter, ) { - $this->translatableStringHelper = $translatableStringHelper; - $this->activityReasonRepository = $activityReasonRepository; - $this->translator = $translator; } public function addRole(): ?string @@ -62,134 +44,97 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt { // create a subquery for activity $sqb = $qb->getEntityManager()->createQueryBuilder(); - $sqb->select('person_person_having_activity.id') + $sqb->select('1') ->from(Activity::class, 'activity_person_having_activity') - ->join('activity_person_having_activity.person', 'person_person_having_activity'); + ->leftJoin('activity_person_having_activity.person', 'person_person_having_activity'); // add clause between date $sqb->where('activity_person_having_activity.date BETWEEN ' - . ':person_having_activity_between_date_from' - . ' AND ' - . ':person_having_activity_between_date_to'); + .':person_having_activity_between_date_from' + .' AND ' + .':person_having_activity_between_date_to' + .' AND ' + .'(person_person_having_activity.id = person.id OR person MEMBER OF activity_person_having_activity.persons)'); - // add clause activity reason - $sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity'); + if (isset($data['reasons']) && [] !== $data['reasons']) { + // add clause activity reason + $sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity'); - $sqb->andWhere( - $sqb->expr()->in( - 'reasons_person_having_activity', - ':person_having_activity_reasons' - ) - ); + $sqb->andWhere( + $sqb->expr()->in( + 'reasons_person_having_activity', + ':person_having_activity_reasons' + ) + ); - $where = $qb->getDQLPart('where'); - $clause = $qb->expr()->in('person.id', $sqb->getDQL()); - - if ($where instanceof Expr\Andx) { - $where->add($clause); - } else { - $where = $qb->expr()->andX($clause); + $qb->setParameter('person_having_activity_reasons', $data['reasons']); } - $qb->add('where', $where); + $qb->andWhere( + $qb->expr()->exists($sqb->getDQL()) + ); + $qb->setParameter( 'person_having_activity_between_date_from', - $data['date_from'] + $this->rollingDateConverter->convert($data['date_from_rolling']) ); $qb->setParameter( 'person_having_activity_between_date_to', - $data['date_to'] + $this->rollingDateConverter->convert($data['date_to_rolling']) ); - $qb->setParameter('person_having_activity_reasons', $data['reasons']); } public function applyOn(): string { - return Declarations::PERSON_IMPLIED_IN; + return Declarations::PERSON_TYPE; } public function buildForm(FormBuilderInterface $builder) { - $builder->add('date_from', DateType::class, [ - 'label' => 'Implied in an activity after this date', - 'attr' => ['class' => 'datepicker'], - 'widget' => 'single_text', - 'format' => 'dd-MM-yyyy', + $builder->add('date_from_rolling', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.person_between_dates.Implied in an activity after this date', ]); - $builder->add('date_to', DateType::class, [ - 'label' => 'Implied in an activity before this date', - 'attr' => ['class' => 'datepicker'], - 'widget' => 'single_text', - 'format' => 'dd-MM-yyyy', + $builder->add('date_to_rolling', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.person_between_dates.Implied in an activity before this date', ]); - $builder->add('reasons', EntityType::class, [ - 'class' => ActivityReason::class, - 'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()), - 'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()), - 'multiple' => true, - 'expanded' => false, - 'label' => 'Activity reasons for those activities', - ]); - - $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { - /** @var FormInterface $filterForm */ - $filterForm = $event->getForm()->getParent(); - $enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData(); - - if (true === $enabled) { - // if the filter is enabled, add some validation - $form = $event->getForm(); - $date_from = $form->get('date_from')->getData(); - $date_to = $form->get('date_to')->getData(); - - // check that fields are not empty - if (null === $date_from) { - $form->get('date_from')->addError(new FormError( - $this->translator->trans('This field ' - . 'should not be empty') - )); - } - - if (null === $date_to) { - $form->get('date_to')->addError(new FormError( - $this->translator->trans('This field ' - . 'should not be empty') - )); - } - - // check that date_from is before date_to - if ( - (null !== $date_from && null !== $date_to) - && $date_from >= $date_to - ) { - $form->get('date_to')->addError(new FormError( - $this->translator->trans('This date ' - . 'should be after the date given in "Implied in an ' - . 'activity after this date" field') - )); - } - } - }); + if ([] !== $reasons = $this->activityReasonRepository->findAll()) { + $builder->add('reasons', EntityType::class, [ + 'class' => ActivityReason::class, + 'choices' => $reasons, + 'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()), + 'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()), + 'multiple' => true, + 'expanded' => false, + 'label' => 'export.filter.activity.person_between_dates.Activity reasons for those activities', + 'help' => 'export.filter.activity.person_between_dates.if no reasons', + ]); + } } + public function getFormDefaultData(): array { - return ['date_from' => new DateTime(), 'date_to' => new DateTime(), 'reasons' => $this->activityReasonRepository->findAll()]; + return [ + 'date_from_rolling' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'date_to_rolling' => new RollingDate(RollingDate::T_TODAY), + 'reasons' => [], + ]; } public function describeAction($data, $format = 'string') { return [ - 'Filtered by person having an activity between %date_from% and ' - . '%date_to% with reasons %reasons_name%', + [] === $data['reasons'] ? + 'export.filter.person_between_dates.describe_action_with_no_subject' + : 'export.filter.person_between_dates.describe_action_with_subject', [ - '%date_from%' => $data['date_from']->format('d-m-Y'), - '%date_to%' => $data['date_to']->format('d-m-Y'), - '%reasons_name%' => implode( + 'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']), + 'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']), + 'reasons' => implode( ', ', array_map( - fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"', + fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"', $data['reasons'] ) ), @@ -199,13 +144,15 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt public function getTitle() { - return 'Filter by person having an activity in a period'; + return 'export.filter.activity.person_between_dates.title'; } public function validateForm($data, ExecutionContextInterface $context) { - if (null === $data['reasons'] || count($data['reasons']) === 0) { - $context->buildViolation('At least one reason must be chosen') + if ($this->rollingDateConverter->convert($data['date_from_rolling']) + >= $this->rollingDateConverter->convert($data['date_to_rolling'])) { + $context->buildViolation('export.filter.activity.person_between_dates.date mismatch') + ->setTranslationDomain('messages') ->addViolation(); } } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php new file mode 100644 index 000000000..51dd60855 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php @@ -0,0 +1,89 @@ +expr()->orX(); + + foreach (array_values($data['accepted_persons']) as $key => $person) { + $orX->add($qb->expr()->isMemberOf(":{$p}_p_{$key}", 'activity.persons')); + $qb->setParameter(":{$p}_p_{$key}", $person); + } + + $qb->andWhere($orX); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_persons', PickPersonDynamicType::class, [ + 'multiple' => true, + 'label' => 'export.filter.activity.by_persons.persons taking part on the activity', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'accepted_persons' => [], + ]; + } + + public function describeAction($data, $format = 'string') + { + $users = []; + + foreach ($data['accepted_persons'] as $u) { + $users[] = $this->personRender->renderString($u, []); + } + + return ['export.filter.activity.by_persons.Filtered activity by persons: only %persons%', [ + '%persons%' => implode(', ', $users), + ]]; + } + + public function getTitle(): string + { + return 'export.filter.activity.by_persons.Filter activity by persons'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php similarity index 83% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php index 0f2a1c7e4..6eee28790 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; @@ -23,17 +23,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; class SentReceivedFilter implements FilterInterface { private const CHOICES = [ - 'is sent' => Activity::SENTRECEIVED_SENT, - 'is received' => Activity::SENTRECEIVED_RECEIVED, + 'export.filter.activity.by_sent_received.is sent' => Activity::SENTRECEIVED_SENT, + 'export.filter.activity.by_sent_received.is received' => Activity::SENTRECEIVED_RECEIVED, ]; private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT; - private TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) + public function __construct(private readonly TranslatorInterface $translator) { - $this->translator = $translator; } public function addRole(): ?string @@ -59,7 +56,7 @@ class SentReceivedFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -69,8 +66,10 @@ class SentReceivedFilter implements FilterInterface 'multiple' => false, 'expanded' => true, 'empty_data' => self::DEFAULT_CHOICE, + 'label' => 'export.filter.activity.by_sent_received.Sent or received', ]); } + public function getFormDefaultData(): array { return ['accepted_sentreceived' => self::DEFAULT_CHOICE]; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php similarity index 88% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php index 9ae988579..54fb87b81 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Export\FilterInterface; @@ -21,11 +21,8 @@ use Symfony\Component\Form\FormBuilderInterface; class UserFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) + public function __construct(private readonly UserRender $userRender) { - $this->userRender = $userRender; } public function addRole(): ?string @@ -51,7 +48,7 @@ class UserFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -61,6 +58,7 @@ class UserFilter implements FilterInterface 'label' => 'Creators', ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php index e85b2d247..0987e7ae9 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php @@ -13,20 +13,24 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; class UsersJobFilter implements FilterInterface { - private TranslatableStringHelperInterface $translatableStringHelper; + private const PREFIX = 'act_filter_user_job'; - public function __construct(TranslatableStringHelperInterface $translatableStringHelper) - { - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly UserJobRepositoryInterface $userJobRepository + ) { } public function addRole(): ?string @@ -36,14 +40,25 @@ class UsersJobFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { + $p = self::PREFIX; + $qb ->andWhere( $qb->expr()->exists( - 'SELECT 1 FROM ' . Activity::class . ' activity_users_job_filter_act - JOIN activity_users_job_filter_act.users users WHERE users.userJob IN (:activity_users_job_filter_jobs) AND activity_users_job_filter_act = activity ' + 'SELECT 1 FROM '.Activity::class." {$p}_act " + ."JOIN {$p}_act.users {$p}_user " + .'JOIN '.UserJobHistory::class." {$p}_history WITH {$p}_history.user = {$p}_user " + ."WHERE {$p}_act = activity " + // job_at based on activity.date + ."AND {$p}_history.startDate <= activity.date " + ."AND ({$p}_history.endDate IS NULL OR {$p}_history.endDate > activity.date) " + ."AND {$p}_history.job IN ( :{$p}_jobs )" ) ) - ->setParameter('activity_users_job_filter_jobs', $data['jobs']); + ->setParameter( + "{$p}_jobs", + $data['jobs'] + ); } public function applyOn() @@ -53,33 +68,38 @@ class UsersJobFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { - $builder->add('jobs', EntityType::class, [ - 'class' => UserJob::class, - 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('jobs', EntityType::class, [ + 'class' => UserJob::class, + 'choices' => $this->userJobRepository->findAllActive(), + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), + 'multiple' => true, + 'expanded' => true, + ]); } public function describeAction($data, $format = 'string') { - return ['export.filter.activity.by_usersjob.Filtered activity by users job: only %jobs%', [ + return ['export.filter.activity.by_users_job.Filtered activity by users job: only %jobs%', [ '%jobs%' => implode( ', ', array_map( fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), - $data['jobs']->toArray() + $data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs'] ) ), ]]; } + public function getFormDefaultData(): array + { + return [ + 'jobs' => [], + ]; + } + public function getTitle() { - return 'export.filter.activity.by_usersjob.Filter by users job'; + return 'export.filter.activity.by_users_job.Filter by users job'; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php index 07ff509ce..61a813dc0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php @@ -14,25 +14,23 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; class UsersScopeFilter implements FilterInterface { - private ScopeRepositoryInterface $scopeRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; + private const PREFIX = 'act_filter_user_scope'; public function __construct( - ScopeRepositoryInterface $scopeRepository, - TranslatableStringHelperInterface $translatableStringHelper + private readonly ScopeRepositoryInterface $scopeRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper ) { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -42,51 +40,66 @@ class UsersScopeFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { + $p = self::PREFIX; + $qb ->andWhere( $qb->expr()->exists( - 'SELECT 1 FROM ' . Activity::class . ' activity_users_scope_filter_act - JOIN activity_users_scope_filter_act.users users WHERE users.mainScope IN (:activity_users_scope_filter_scopes) AND activity_users_scope_filter_act = activity ' + 'SELECT 1 FROM '.Activity::class." {$p}_act " + ."JOIN {$p}_act.users {$p}_user " + .'JOIN '.UserScopeHistory::class." {$p}_history WITH {$p}_history.user = {$p}_user " + ."WHERE {$p}_act = activity " + // scope_at based on activity.date + ."AND {$p}_history.startDate <= activity.date " + ."AND ({$p}_history.endDate IS NULL OR {$p}_history.endDate > activity.date) " + ."AND {$p}_history.scope IN ( :{$p}_scopes )" ) ) - ->setParameter('activity_users_scope_filter_scopes', $data['scopes']); + ->setParameter( + "{$p}_scopes", + $data['scopes'] + ); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { - $builder->add('scopes', EntityType::class, [ - 'class' => Scope::class, - 'choices' => $this->scopeRepository->findAllActive(), - 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('scopes', EntityType::class, [ + 'class' => Scope::class, + 'choices' => $this->scopeRepository->findAllActive(), + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + 'multiple' => true, + 'expanded' => true, + ]); } - public function describeAction($data, $format = 'string') + public function describeAction($data, $format = 'string'): array { - return ['export.filter.activity.by_usersscope.Filtered activity by users scope: only %scopes%', [ + return ['export.filter.activity.by_users_scope.Filtered activity by users scope: only %scopes%', [ '%scopes%' => implode( ', ', array_map( fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), - $data['scopes']->toArray() + $data['scopes'] instanceof Collection ? $data['scopes']->toArray() : $data['scopes'] ) ), ]]; } - public function getTitle() + public function getFormDefaultData(): array { - return 'export.filter.activity.by_usersscope.Filter by users scope'; + return [ + 'scopes' => [], + ]; + } + + public function getTitle(): string + { + return 'export.filter.activity.by_users_scope.Filter by users scope'; } } diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index a74178afd..9e2358e8b 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -15,11 +15,10 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityPresence; use Chill\ActivityBundle\Form\Type\PickActivityReasonType; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; -use Chill\DocStoreBundle\Form\StoredObjectType; +use Chill\DocStoreBundle\Form\CollectionStoredObjectType; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\User; -use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\PickUserDynamicType; @@ -34,12 +33,8 @@ use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Chill\ThirdPartyBundle\Entity\ThirdParty; -use DateInterval; -use DateTime; -use DateTimeZone; use Doctrine\ORM\EntityRepository; use Doctrine\Persistence\ObjectManager; -use RuntimeException; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; @@ -53,45 +48,24 @@ use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Role\Role; -use function in_array; - class ActivityType extends AbstractType { - protected AuthorizationHelper $authorizationHelper; - - protected ObjectManager $om; - - protected SocialActionRender $socialActionRender; - - protected SocialIssueRender $socialIssueRender; - - protected array $timeChoices; - - protected TranslatableStringHelper $translatableStringHelper; - protected User $user; public function __construct( TokenStorageInterface $tokenStorage, - AuthorizationHelper $authorizationHelper, - ObjectManager $om, - TranslatableStringHelper $translatableStringHelper, - array $timeChoices, - SocialIssueRender $socialIssueRender, - SocialActionRender $socialActionRender + protected AuthorizationHelper $authorizationHelper, + protected ObjectManager $om, + protected TranslatableStringHelper $translatableStringHelper, + protected array $timeChoices, + protected SocialIssueRender $socialIssueRender, + protected SocialActionRender $socialActionRender ) { if (!$tokenStorage->getToken()->getUser() instanceof User) { - throw new RuntimeException('you should have a valid user'); + throw new \RuntimeException('you should have a valid user'); } $this->user = $tokenStorage->getToken()->getUser(); - $this->authorizationHelper = $authorizationHelper; - $this->om = $om; - $this->translatableStringHelper = $translatableStringHelper; - $this->timeChoices = $timeChoices; - $this->socialIssueRender = $socialIssueRender; - $this->socialActionRender = $socialActionRender; } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -112,8 +86,7 @@ class ActivityType extends AbstractType /** @var \Chill\ActivityBundle\Entity\ActivityType $activityType */ $activityType = $options['activityType']; - // TODO revoir la gestion des center au niveau du form des activité. - if ($options['center'] instanceof Center && null !== $options['data']->getPerson()) { + if (null !== $options['data']->getPerson()) { $builder->add('scope', ScopePickerType::class, [ 'center' => $options['center'], 'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'], @@ -121,16 +94,16 @@ class ActivityType extends AbstractType ]); } - /** @var ? \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingPeriod */ + /** @var AccompanyingPeriod|null $accompanyingPeriod */ $accompanyingPeriod = null; if ($options['accompanyingPeriod'] instanceof AccompanyingPeriod) { $accompanyingPeriod = $options['accompanyingPeriod']; } - if ($activityType->isVisible('socialIssues') && $accompanyingPeriod) { + if ($activityType->isVisible('socialIssues') && null !== $accompanyingPeriod) { $builder->add('socialIssues', HiddenType::class, [ - 'required' => $activityType->getSocialIssuesVisible() === 2, + 'required' => 2 === $activityType->getSocialIssuesVisible(), ]); $builder->get('socialIssues') ->addModelTransformer(new CallbackTransformer( @@ -156,9 +129,9 @@ class ActivityType extends AbstractType )); } - if ($activityType->isVisible('socialActions') && $accompanyingPeriod) { + if ($activityType->isVisible('socialActions') && null !== $accompanyingPeriod) { $builder->add('socialActions', HiddenType::class, [ - 'required' => $activityType->getSocialActionsVisible() === 2, + 'required' => 2 === $activityType->getSocialActionsVisible(), ]); $builder->get('socialActions') ->addModelTransformer(new CallbackTransformer( @@ -209,6 +182,7 @@ class ActivityType extends AbstractType $builder->add('attendee', EntityType::class, [ 'label' => $activityType->getLabel('attendee'), 'required' => $activityType->isRequired('attendee'), + 'placeholder' => false, 'expanded' => true, 'class' => ActivityPresence::class, 'choice_label' => fn (ActivityPresence $activityPresence) => $this->translatableStringHelper->localize($activityPresence->getName()), @@ -217,7 +191,7 @@ class ActivityType extends AbstractType ]); } - if ($activityType->isVisible('user') && $options['center'] instanceof Center) { + if ($activityType->isVisible('user')) { $builder->add('user', PickUserDynamicType::class, [ 'label' => $activityType->getLabel('user'), 'required' => $activityType->isRequired('user'), @@ -301,16 +275,9 @@ class ActivityType extends AbstractType } if ($activityType->isVisible('documents')) { - $builder->add('documents', ChillCollectionType::class, [ - 'entry_type' => StoredObjectType::class, + $builder->add('documents', CollectionStoredObjectType::class, [ 'label' => $activityType->getLabel('documents'), 'required' => $activityType->isRequired('documents'), - 'allow_add' => true, - 'allow_delete' => true, - 'button_add_label' => 'activity.Insert a document', - 'button_remove_label' => 'activity.Remove a document', - 'empty_collection_explain' => 'No documents', - 'entry_options' => ['has_title' => true], ]); } @@ -342,7 +309,7 @@ class ActivityType extends AbstractType if ($activityType->isVisible('location')) { $builder->add('location', HiddenType::class, [ - 'required' => $activityType->getLocationVisible() === 2, + 'required' => 2 === $activityType->getLocationVisible(), ]) ->get('location') ->addModelTransformer(new CallbackTransformer( @@ -393,16 +360,16 @@ class ActivityType extends AbstractType ) { // set the timezone to GMT, and fix the difference between current and GMT // the datetimetransformer will then handle timezone as GMT - $timezoneUTC = new DateTimeZone('GMT'); - /** @var DateTime $data */ - $data = $formEvent->getData() ?? DateTime::createFromFormat('U', '300'); + $timezoneUTC = new \DateTimeZone('GMT'); + /** @var \DateTime $data */ + $data = $formEvent->getData() ?? \DateTime::createFromFormat('U', '300'); $seconds = $data->getTimezone()->getOffset($data); $data->setTimeZone($timezoneUTC); - $data->add(new DateInterval('PT' . $seconds . 'S')); + $data->add(new \DateInterval('PT'.$seconds.'S')); // test if the timestamp is in the choices. // If not, recreate the field with the new timestamp - if (!in_array($data->getTimestamp(), $timeChoices, true)) { + if (!\in_array($data->getTimestamp(), $timeChoices, true)) { // the data are not in the possible values. add them $timeChoices[$data->format('H:i')] = $data->getTimestamp(); $form = $builder->create($fieldName, ChoiceType::class, array_merge( @@ -427,10 +394,10 @@ class ActivityType extends AbstractType $resolver ->setRequired(['center', 'role', 'activityType', 'accompanyingPeriod']) - ->setAllowedTypes('center', ['null', Center::class]) - ->setAllowedTypes('role', [Role::class, 'string']) + ->setAllowedTypes('center', ['null', Center::class, 'array']) + ->setAllowedTypes('role', ['string']) ->setAllowedTypes('activityType', \Chill\ActivityBundle\Entity\ActivityType::class) - ->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null']); + ->setAllowedTypes('accompanyingPeriod', [AccompanyingPeriod::class, 'null']); } public function getBlockPrefix(): string diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php b/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php index 073b8099f..b4baa2f54 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php @@ -25,11 +25,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class ActivityTypeType extends AbstractType { - private TranslatableStringHelper $translatableStringHelper; - - public function __construct(TranslatableStringHelper $translatableStringHelper) + public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) { - $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -61,8 +58,8 @@ class ActivityTypeType extends AbstractType foreach ($fields as $field) { $builder - ->add($field . 'Visible', ActivityFieldPresence::class) - ->add($field . 'Label', TextType::class, [ + ->add($field.'Visible', ActivityFieldPresence::class) + ->add($field.'Label', TextType::class, [ 'required' => false, 'empty_data' => '', ]); diff --git a/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php b/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php index 35da7e02f..009d7b29f 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php @@ -24,20 +24,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver; */ class PickActivityReasonType extends AbstractType { - private ActivityReasonRepository $activityReasonRepository; - - private ActivityReasonRender $reasonRender; - - private TranslatableStringHelperInterface $translatableStringHelper; - public function __construct( - ActivityReasonRepository $activityReasonRepository, - ActivityReasonRender $reasonRender, - TranslatableStringHelperInterface $translatableStringHelper + private readonly ActivityReasonRepository $activityReasonRepository, + private readonly ActivityReasonRender $reasonRender, + private readonly TranslatableStringHelperInterface $translatableStringHelper ) { - $this->activityReasonRepository = $activityReasonRepository; - $this->reasonRender = $reasonRender; - $this->translatableStringHelper = $translatableStringHelper; } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php index 4aa259097..d94ac34e1 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php @@ -23,14 +23,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class TranslatableActivityReasonCategoryType extends AbstractType { - private TranslatableStringHelperInterface $translatableStringHelper; - - private TranslatorInterface $translator; - - public function __construct(TranslatableStringHelperInterface $translatableStringHelper, TranslatorInterface $translator) + public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatorInterface $translator) { - $this->translatableStringHelper = $translatableStringHelper; - $this->translator = $translator; } public function configureOptions(OptionsResolver $resolver) @@ -39,16 +33,11 @@ class TranslatableActivityReasonCategoryType extends AbstractType [ 'class' => ActivityReasonCategory::class, 'choice_label' => fn (ActivityReasonCategory $category) => $this->translatableStringHelper->localize($category->getName()) - . (!$category->getActive() ? ' (' . $this->translator->trans('inactive') . ')' : ''), + .(!$category->getActive() ? ' ('.$this->translator->trans('inactive').')' : ''), ] ); } - public function getBlockPrefix() - { - return 'translatable_activity_reason_category'; - } - public function getParent() { return EntityType::class; diff --git a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityType.php b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityType.php index d4807b82d..e2233f3b1 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityType.php @@ -20,16 +20,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class TranslatableActivityType extends AbstractType { - protected ActivityTypeRepositoryInterface $activityTypeRepository; - - protected TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct( - TranslatableStringHelperInterface $helper, - ActivityTypeRepositoryInterface $activityTypeRepository - ) { - $this->translatableStringHelper = $helper; - $this->activityTypeRepository = $activityTypeRepository; + public function __construct(protected TranslatableStringHelperInterface $translatableStringHelper, protected ActivityTypeRepositoryInterface $activityTypeRepository) + { } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php index 9884450ec..bae2b0577 100644 --- a/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -23,16 +23,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface { - protected Security $security; - - protected TranslatorInterface $translator; - - public function __construct( - Security $security, - TranslatorInterface $translator - ) { - $this->security = $security; - $this->translator = $translator; + public function __construct(protected Security $security, protected TranslatorInterface $translator) + { } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php index 4b2d348d7..8337a3582 100644 --- a/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php @@ -18,13 +18,10 @@ use Symfony\Component\Security\Core\Security; /** * @implements LocalMenuBuilderInterface */ -final class AdminMenuBuilder implements LocalMenuBuilderInterface +final readonly class AdminMenuBuilder implements LocalMenuBuilderInterface { - private Security $security; - - public function __construct(Security $security) + public function __construct(private Security $security) { - $this->security = $security; } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php index a3cebec38..180247808 100644 --- a/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php @@ -21,29 +21,15 @@ use Symfony\Contracts\Translation\TranslatorInterface; /** * @implements LocalMenuBuilderInterface */ -final class PersonMenuBuilder implements LocalMenuBuilderInterface +final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface { - /** - * @var AuthorizationCheckerInterface - */ - private $authorizationChecker; - - /** - * @var TranslatorInterface - */ - private $translator; - - public function __construct( - AuthorizationCheckerInterface $authorizationChecker, - TranslatorInterface $translator - ) { - $this->translator = $translator; - $this->authorizationChecker = $authorizationChecker; + public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator) + { } public function buildMenu($menuId, MenuItem $menu, array $parameters) { - /** @var \Chill\PersonBundle\Entity\Person $person */ + /** @var Person $person */ $person = $parameters['person']; if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) { diff --git a/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php b/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php index ab26da81d..55b831d8b 100644 --- a/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php +++ b/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php @@ -16,13 +16,10 @@ use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Notification\NotificationHandlerInterface; -final class ActivityNotificationHandler implements NotificationHandlerInterface +final readonly class ActivityNotificationHandler implements NotificationHandlerInterface { - private ActivityRepository $activityRepository; - - public function __construct(ActivityRepository $activityRepository) + public function __construct(private ActivityRepository $activityRepository) { - $this->activityRepository = $activityRepository; } public function getTemplate(Notification $notification, array $options = []): string @@ -40,6 +37,6 @@ final class ActivityNotificationHandler implements NotificationHandlerInterface public function supports(Notification $notification, array $options = []): bool { - return $notification->getRelatedEntityClass() === Activity::class; + return Activity::class === $notification->getRelatedEntityClass(); } } diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 1f2039a2c..6d9991e6d 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -18,65 +18,207 @@ use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Entity\Scope; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\Query\ResultSetMappingBuilder; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Role\Role; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Security; -use function count; -use function in_array; - -final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface +final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface { - private AuthorizationHelper $authorizationHelper; - - private CenterResolverDispatcherInterface $centerResolverDispatcher; - - private EntityManagerInterface $em; - - private ActivityRepository $repository; - - private Security $security; - - private TokenStorageInterface $tokenStorage; - public function __construct( - AuthorizationHelper $authorizationHelper, - CenterResolverDispatcherInterface $centerResolverDispatcher, - TokenStorageInterface $tokenStorage, - ActivityRepository $repository, - EntityManagerInterface $em, - Security $security + private AuthorizationHelperForCurrentUserInterface $authorizationHelper, + private CenterResolverManagerInterface $centerResolverManager, + private ActivityRepository $repository, + private EntityManagerInterface $em, + private Security $security, + private RequestStack $requestStack, ) { - $this->authorizationHelper = $authorizationHelper; - $this->centerResolverDispatcher = $centerResolverDispatcher; - $this->tokenStorage = $tokenStorage; - $this->repository = $repository; - $this->em = $em; - $this->security = $security; } - public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int { - $user = $this->security->getUser(); - $center = $this->centerResolverDispatcher->resolveCenter($period); + $qb = $this->buildBaseQuery($filters); - if (0 === count($orderBy)) { - $orderBy = ['date' => 'DESC']; + $qb + ->select('COUNT(a)') + ->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period); + + return $qb->getQuery()->getSingleScalarResult(); + } + + public function countByPerson(Person $person, string $role, array $filters = []): int + { + $qb = $this->buildBaseQuery($filters); + + $qb = $this->filterBaseQueryByPerson($qb, $person, $role); + + $qb->select('COUNT(a)'); + + return $qb->getQuery()->getSingleScalarResult(); + } + + public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array + { + $qb = $this->buildBaseQuery($filters); + + $qb->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period); + + foreach ($orderBy as $field => $order) { + $qb->addOrderBy('a.'.$field, $order); } - $scopes = $this->authorizationHelper - ->getReachableCircles($user, $role, $center); + if (null !== $start) { + $qb->setFirstResult($start); + } + if (null !== $limit) { + $qb->setMaxResults($limit); + } - return $this->em->getRepository(Activity::class) - ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy); + return $qb->getQuery()->getResult(); + } + + public function buildBaseQuery(array $filters): QueryBuilder + { + $qb = $this->repository + ->createQueryBuilder('a') + ; + + if (($filters['my_activities'] ?? false) and ($user = $this->security->getUser()) instanceof User) { + $qb->andWhere( + $qb->expr()->orX( + 'a.createdBy = :user', + 'a.user = :user', + ':user MEMBER OF a.users' + ) + )->setParameter('user', $user); + } + + if ([] !== ($types = $filters['types'] ?? [])) { + $qb->andWhere('a.activityType IN (:types)')->setParameter('types', $types); + } + + if ([] !== ($jobs = $filters['jobs'] ?? [])) { + $qb + ->leftJoin('a.createdBy', 'creator') + ->leftJoin('a.user', 'activity_u') + ->andWhere( + $qb->expr()->orX( + $qb->expr()->exists( + sprintf( + 'SELECT 1 FROM %s ujh_creator WHERE ujh_creator.user = a.createdBy ' + .'AND ujh_creator.job IN (:jobs) AND a.createdAt > ujh_creator.startDate ' + .'AND (ujh_creator.endDate IS NULL or ujh_creator.endDate > a.date)', + User\UserJobHistory::class + ) + ), + $qb->expr()->exists( + sprintf( + 'SELECT 1 FROM %s ujh_u WHERE ujh_u.user = a.user ' + .'AND ujh_u.job IN (:jobs) AND a.createdAt > ujh_u.startDate ' + .'AND (ujh_u.endDate IS NULL or ujh_u.endDate > a.date)', + User\UserJobHistory::class + ) + ), + $qb->expr()->exists( + sprintf( + 'SELECT 1 FROM %s ujh_users WHERE ujh_users.user MEMBER OF a.users ' + .'AND ujh_users.job IN (:jobs) AND a.createdAt > ujh_users.startDate ' + .'AND (ujh_users.endDate IS NULL or ujh_users.endDate > a.date)', + User\UserJobHistory::class + ) + ), + ) + ) + ->setParameter('jobs', $jobs); + } + + if (null !== ($after = $filters['after'] ?? null)) { + $qb->andWhere('a.date >= :after')->setParameter('after', $after); + } + + if (null !== ($before = $filters['before'] ?? null)) { + $qb->andWhere('a.date <= :before')->setParameter('before', $before); + } + + return $qb; + } + + /** + * @return array + */ + public function findActivityTypeByAssociated(AccompanyingPeriod|Person $associated): array + { + $in = $this->em->createQueryBuilder(); + $in + ->select('1') + ->from(Activity::class, 'a'); + + if ($associated instanceof Person) { + $in = $this->filterBaseQueryByPerson($in, $associated, ActivityVoter::SEE); + } else { + $in->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $associated); + } + + // join between the embedded exist query and the main query + $in->andWhere('a.activityType = t'); + + $qb = $this->em->createQueryBuilder()->setParameters($in->getParameters()); + $qb + ->select('t') + ->from(ActivityType::class, 't') + ->where( + $qb->expr()->exists($in->getDQL()) + ); + + return $qb->getQuery()->getResult(); + } + + public function findUserJobByAssociated(AccompanyingPeriod|Person $associated): array + { + $in = $this->em->createQueryBuilder(); + $in->select('IDENTITY(u.job)') + ->distinct() + ->from(User\UserJobHistory::class, 'u') + ->join( + Activity::class, + 'a', + Join::WITH, + 'a.createdBy = u.user OR a.user = u.user OR u.user MEMBER OF a.users AND a.date >= u.startDate ANd (u.endDate IS NULL or u.endDate > a.date)' + ); + + if ($associated instanceof Person) { + $in = $this->filterBaseQueryByPerson($in, $associated, ActivityVoter::SEE); + } else { + $in->andWhere('a.accompanyingPeriod = :associated'); + $in->setParameter('associated', $associated); + } + + $qb = $this->em->createQueryBuilder()->setParameters($in->getParameters()); + + $qb->select('ub', 'JSON_EXTRACT(ub.label, :lang) AS HIDDEN lang') + ->from(UserJob::class, 'ub') + ->where($qb->expr()->in('ub.id', $in->getDQL())) + ->setParameter('lang', $this->requestStack->getCurrentRequest()->getLocale()) + ->orderBy('lang') + ; + + return $qb->getQuery()->getResult(); } public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array @@ -101,7 +243,8 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte thirdparties.thirdpartyids, persons.personids, actions.socialactionids, - issues.socialissueids + issues.socialissueids, + a.user_id FROM activity a LEFT JOIN chill_main_location location ON a.location_id = location.id @@ -141,6 +284,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte ->addJoinedEntityResult(ActivityPresence::class, 'activityPresence', 'a', 'attendee') ->addFieldResult('activityPresence', 'presence_id', 'id') ->addFieldResult('activityPresence', 'presence_name', 'name') + ->addScalarResult('user_id', 'userId', Types::INTEGER) // results which cannot be mapped into entity ->addScalarResult('comment_comment', 'comment', Types::TEXT) @@ -159,25 +303,73 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte return $nq->getResult(AbstractQuery::HYDRATE_ARRAY); } - /** - * @param array $orderBy - * - * @return Activity[]|array - */ - public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array + public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): array { - $user = $this->security->getUser(); - $center = $this->centerResolverDispatcher->resolveCenter($person); + $qb = $this->buildBaseQuery($filters); - if (0 === count($orderBy)) { - $orderBy = ['date' => 'DESC']; + $qb = $this->filterBaseQueryByPerson($qb, $person, $role); + + foreach ($orderBy as $field => $direction) { + $qb->addOrderBy('a.'.$field, $direction); } - $reachableScopes = $this->authorizationHelper - ->getReachableCircles($user, $role, $center); + if (null !== $start) { + $qb->setFirstResult($start); + } + if (null !== $limit) { + $qb->setMaxResults($limit); + } - return $this->em->getRepository(Activity::class) - ->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start); + return $qb->getQuery()->getResult(); + } + + private function filterBaseQueryByPerson(QueryBuilder $qb, Person $person, string $role): QueryBuilder + { + $orX = $qb->expr()->orX(); + $counter = 0; + foreach ($this->centerResolverManager->resolveCenters($person) as $center) { + $scopes = $this->authorizationHelper->getReachableScopes($role, $center); + + if ([] === $scopes) { + continue; + } + + $orX->add(sprintf('a.person = :person AND a.scope IN (:scopes_%d)', $counter)); + $qb->setParameter(sprintf('scopes_%d', $counter), $scopes); + $qb->setParameter('person', $person); + ++$counter; + } + + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $and = $qb->expr()->andX( + sprintf('a.accompanyingPeriod = :period_%d', $counter), + sprintf('a.date >= :participation_start_%d', $counter) + ); + + $qb + ->setParameter(sprintf('period_%d', $counter), $participation->getAccompanyingPeriod()) + ->setParameter(sprintf('participation_start_%d', $counter), $participation->getStartDate()); + + if (null !== $participation->getEndDate()) { + $and->add(sprintf('a.date < :participation_end_%d', $counter)); + $qb + ->setParameter(sprintf('participation_end_%d', $counter), $participation->getEndDate()); + } + $orX->add($and); + ++$counter; + } + + if (0 === $orX->count()) { + $qb->andWhere('FALSE = TRUE'); + } else { + $qb->andWhere($orX); + } + + return $qb; } public function queryTimelineIndexer(string $context, array $args = []): array @@ -189,10 +381,10 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte return [ 'id' => $metadataActivity->getTableName() - . '.' . $metadataActivity->getColumnName('id'), + .'.'.$metadataActivity->getColumnName('id'), 'type' => 'activity', 'date' => $metadataActivity->getTableName() - . '.' . $metadataActivity->getColumnName('date'), + .'.'.$metadataActivity->getColumnName('date'), 'FROM' => $from, 'WHERE' => $where, 'parameters' => $parameters, @@ -205,12 +397,12 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte $metadataPerson = $this->em->getClassMetadata(Person::class); $associationMapping = $metadataActivity->getAssociationMapping('person'); - return $metadataActivity->getTableName() . ' JOIN ' - . $metadataPerson->getTableName() . ' ON ' - . $metadataPerson->getTableName() . '.' . + return $metadataActivity->getTableName().' JOIN ' + .$metadataPerson->getTableName().' ON ' + .$metadataPerson->getTableName().'.'. $associationMapping['joinColumns'][0]['referencedColumnName'] - . ' = ' - . $associationMapping['joinColumns'][0]['name']; + .' = ' + .$associationMapping['joinColumns'][0]['name']; } private function getWhereClause(string $context, array $args): array @@ -226,11 +418,10 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte // acls: $reachableCenters = $this->authorizationHelper->getReachableCenters( - $this->tokenStorage->getToken()->getUser(), ActivityVoter::SEE ); - if (count($reachableCenters) === 0) { + if (0 === \count($reachableCenters)) { // insert a dummy condition return 'FALSE = TRUE'; } @@ -247,11 +438,11 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte foreach ($reachableCenters as $center) { // we pass if not in centers - if (!in_array($center, $args['centers'], true)) { + if (!\in_array($center, $args['centers'], true)) { continue; } // we get all the reachable scopes for this center - $reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), ActivityVoter::SEE, $center); + $reachableScopes = $this->authorizationHelper->getReachableScopes(ActivityVoter::SEE, $center); // we get the ids for those scopes $reachablesScopesId = array_map( static fn (Scope $scope) => $scope->getId(), @@ -269,7 +460,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte // begin loop for scopes $where .= ' AND ('; - $scopesI = 0; //like scope#i + $scopesI = 0; // like scope#i foreach ($reachablesScopesId as $scopeId) { if (0 < $scopesI) { diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php index 8cdb83524..7c51afd63 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php @@ -11,15 +11,32 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Repository; +use Chill\ActivityBundle\Entity\Activity; +use Chill\ActivityBundle\Entity\ActivityType; +use Chill\MainBundle\Entity\UserJob; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; interface ActivityACLAwareRepositoryInterface { /** - * @return Activity[]|array + * Return all the activities associated to an accompanying period and that the user is allowed to apply the given role. + * + * @param array{my_activities?: bool, types?: array, jobs?: array, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters + * + * @return array */ - public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; + public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array; + + /** + * @param array{my_activities?: bool, types?: array, jobs?: array, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters + */ + public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int; + + /** + * @param array{my_activities?: bool, types?: array, jobs?: array, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters + */ + public function countByPerson(Person $person, string $role, array $filters = []): int; /** * Return a list of activities, simplified as array (not object). @@ -31,7 +48,28 @@ interface ActivityACLAwareRepositoryInterface public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array; /** - * @return Activity[]|array + * @param array{my_activities?: bool, types?: array, jobs?: array, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters + * + * @return array */ - public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; + public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array; + + /** + * Return a list of the type for the activities associated to person or accompanying period. + * + * @return array + */ + public function findActivityTypeByAssociated(AccompanyingPeriod|Person $associated): array; + + /** + * Return a list of the user job for the activities associated to person or accompanying period. + * + * Associated mean the job: + * - of the creator; + * - of the user (activity.user) + * - of all the users + * + * @return array + */ + public function findUserJobByAssociated(AccompanyingPeriod|Person $associated): array; } diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php index ce70409ba..0623601a5 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -15,25 +15,15 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider; use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocProvider; -use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; -use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider; -use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface; -use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; -use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; -use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\MappingException; -use Doctrine\ORM\QueryBuilder; -use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\Security\Core\Security; final readonly class ActivityDocumentACLAwareRepository implements ActivityDocumentACLAwareRepositoryInterface @@ -46,14 +36,14 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum ) { } - public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface + public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface { $query = $this->buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content); return $this->addFetchQueryByPersonACL($query, $person); } - public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); $activityMetadata = $this->em->getClassMetadata(Activity::class); @@ -82,7 +72,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum return $this->addWhereClauses($query, $startDate, $endDate, $content); } - public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); $activityMetadata = $this->em->getClassMetadata(Activity::class); @@ -117,8 +107,8 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum $storedObjectMetadata->getColumnName('createdAt') ); $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), - DateTimeImmutable::createFromInterface($participation->getStartDate()), - null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; + \DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : \DateTimeImmutable::createFromInterface($participation->getEndDate())]; $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; } @@ -133,7 +123,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum return $this->addWhereClauses($query, $startDate, $endDate, $content); } - private function addWhereClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + private function addWhereClauses(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); @@ -156,7 +146,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum if (null !== $content and '' !== $content) { $query->addWhereClause( 'stored_obj.title ilike ?', - ['%' . $content . '%'], + ['%'.$content.'%'], [Types::STRING] ); } @@ -173,7 +163,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum foreach ($this->centerResolverManager->resolveCenters($person) as $center) { $reachableScopes = [ ...$reachableScopes, - ...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center) + ...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center), ]; } diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php index 9f4a9c0f8..79c320ba2 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php @@ -14,24 +14,23 @@ namespace Chill\ActivityBundle\Repository; use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\PersonBundle\Entity\Person; -use DateTimeImmutable; /** - * Gives queries usable for fetching documents, with ACL aware + * Gives queries usable for fetching documents, with ACL aware. */ interface ActivityDocumentACLAwareRepositoryInterface { /** - * Return a fetch query for querying document's activities for a person + * Return a fetch query for querying document's activities for a person. * * This method must check the rights to see a document: the user must be allowed to see the given activities */ - public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface; + public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface; /** - * Return a fetch query for querying document's activities for an activity in accompanying periods, but for a given person + * Return a fetch query for querying document's activities for an activity in accompanying periods, but for a given person. * * This method must check the rights to see a document: the user must be allowed to see the given accompanying periods */ - public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery; + public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery; } diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepository.php index 2cf9f9470..3ed96074b 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepository.php @@ -17,7 +17,7 @@ use Doctrine\ORM\EntityRepository; class ActivityPresenceRepository implements ActivityPresenceRepositoryInterface { - private EntityRepository $repository; + private readonly EntityRepository $repository; public function __construct(EntityManagerInterface $entityManager) { diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php index c6b69319e..47a7b35e8 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php @@ -23,15 +23,11 @@ use Symfony\Component\HttpFoundation\RequestStack; */ class ActivityReasonRepository extends ServiceEntityRepository { - private RequestStack $requestStack; - public function __construct( ManagerRegistry $registry, - RequestStack $requestStack + private readonly RequestStack $requestStack ) { parent::__construct($registry, ActivityReason::class); - - $this->requestStack = $requestStack; } /** diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php index 5a6e16cd5..a7025d4a9 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php @@ -62,7 +62,7 @@ class ActivityRepository extends ServiceEntityRepository ->setParameter('period', $period); foreach ($orderBy as $k => $dir) { - $qb->addOrderBy('a.' . $k, $dir); + $qb->addOrderBy('a.'.$k, $dir); } $qb->setMaxResults($limit)->setFirstResult($offset); @@ -90,7 +90,7 @@ class ActivityRepository extends ServiceEntityRepository ->setParameter('person', $person); foreach ($orderBy as $k => $dir) { - $qb->addOrderBy('a.' . $k, $dir); + $qb->addOrderBy('a.'.$k, $dir); } $qb->setMaxResults($limit)->setFirstResult($offset); diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepository.php index fd5d52fce..99adf4fe0 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepository.php @@ -17,7 +17,7 @@ use Doctrine\ORM\EntityRepository; final class ActivityTypeRepository implements ActivityTypeRepositoryInterface { - private EntityRepository $repository; + private readonly EntityRepository $repository; public function __construct(EntityManagerInterface $em) { diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php index 2148a02f5..899535ad6 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php @@ -17,7 +17,7 @@ use Doctrine\Persistence\ObjectRepository; interface ActivityTypeRepositoryInterface extends ObjectRepository { /** - * @return array|ActivityType[] + * @return array */ public function findAllActive(): array; } diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig index 8d1aba2b3..b3382c3de 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig @@ -75,7 +75,7 @@ {% endif %} - {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { + {% include '@ChillActivity/Activity/concernedGroups.html.twig' with { 'context': context, 'render': 'wrap-list', 'entity': activity, diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/edit.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/edit.html.twig index a000b0c7e..d986f9150 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/edit.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/edit.html.twig @@ -92,7 +92,9 @@ {% endif %} {%- if edit_form.documents is defined -%} - {{ form_row(edit_form.documents) }} + {{ form_label(edit_form.documents) }} + {{ form_errors(edit_form.documents) }} + {{ form_widget(edit_form.documents) }}
{% endif %} @@ -127,4 +129,4 @@ {% block css %} {{ encore_entry_link_tags('mod_pickentity_type') }} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/editAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/editAccompanyingCourse.html.twig index d0359e223..3cf476bbb 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/editAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/editAccompanyingCourse.html.twig @@ -8,7 +8,7 @@
{# <=== vue component #} - {% include 'ChillActivityBundle:Activity:edit.html.twig' %} + {% include '@ChillActivity/Activity/edit.html.twig' %}
{% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/editPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/editPerson.html.twig index 91b3867c1..73eec5f62 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/editPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/editPerson.html.twig @@ -24,7 +24,7 @@
{# <=== vue component #} - {% include 'ChillActivityBundle:Activity:edit.html.twig' %} + {% include '@ChillActivity/Activity/edit.html.twig' %}
{% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig index 79c946f17..fde256af0 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig @@ -80,15 +80,18 @@
+ {{ filter|chill_render_filter_order_helper }} + {% if activities|length == 0 %}

{{ "There isn't any activities."|trans }}

{% else %} +
{% for activity in activities %} - {% include 'ChillActivityBundle:Activity:_list_item.html.twig' with { + {% include '@ChillActivity/Activity/_list_item.html.twig' with { 'context': context, 'recordAction': _self.recordAction(activity, context, person_id, accompanying_course_id) } %} @@ -96,4 +99,6 @@
{% endif %} + {{ chill_pagination(paginator) }} +
diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig index bdf55d86f..e2710ea43 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig @@ -31,7 +31,7 @@

{{ 'Activity list' |trans }}

- {% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'accompanyingCourse'} %} + {% include '@ChillActivity/Activity/list.html.twig' with {'context': 'accompanyingCourse'} %} {% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) %}
    diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig index 0c1afdac9..5870f124f 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig @@ -46,7 +46,7 @@

    {{ 'Activity list' |trans }}

    - {% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'person'} %} + {% include '@ChillActivity/Activity/list.html.twig' with {'context': 'person'} %} {% if is_granted('CHILL_ACTIVITY_CREATE', person) %}
      diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/newAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/newAccompanyingCourse.html.twig index 47a61bd86..0b098dffe 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/newAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/newAccompanyingCourse.html.twig @@ -8,7 +8,7 @@
      {# <=== vue component #} - {% include 'ChillActivityBundle:Activity:new.html.twig' with {'context': 'accompanyingCourse'} %} + {% include '@ChillActivity/Activity/new.html.twig' with {'context': 'accompanyingCourse'} %}
      {% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/newPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/newPerson.html.twig index d57fc3412..dd0b91ccb 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/newPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/newPerson.html.twig @@ -8,7 +8,7 @@
      {# <=== vue component #} - {% include 'ChillActivityBundle:Activity:new.html.twig' with {'context': 'person'} %} + {% include '@ChillActivity/Activity/new.html.twig' with {'context': 'person'} %}
      {% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectTypeAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectTypeAccompanyingCourse.html.twig index 5e73db597..4bc26baea 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectTypeAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectTypeAccompanyingCourse.html.twig @@ -5,5 +5,5 @@ {% block title 'Activity creation'|trans %} {% block content %} - {% include 'ChillActivityBundle:Activity:selectType.html.twig' %} + {% include '@ChillActivity/Activity/selectType.html.twig' %} {% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectTypePerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectTypePerson.html.twig index f616012e4..c3815f79d 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectTypePerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectTypePerson.html.twig @@ -5,5 +5,5 @@ {% block title 'Activity creation'|trans %} {% block content %} - {% include 'ChillActivityBundle:Activity:selectType.html.twig' %} + {% include '@ChillActivity/Activity/selectType.html.twig' %} {% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig index fca6a7658..d4beb606a 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig @@ -88,7 +88,7 @@

      {{ 'Concerned groups'|trans }}

      -{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { +{% include '@ChillActivity/Activity/concernedGroups.html.twig' with { 'context': context, 'render': 'bloc', 'badge_person': true diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig index 3486f47bc..de471a9ab 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig @@ -18,11 +18,11 @@ {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} -{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} +{% import '@ChillActivity/ActivityReason/macro.html.twig' as m %} {% block content -%}
      - {% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'accompanyingCourse'} %} + {% include '@ChillActivity/Activity/show.html.twig' with {'context': 'accompanyingCourse'} %}
      {% endblock content %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showInNotification.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showInNotification.html.twig index 721538271..36ba70c7b 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showInNotification.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showInNotification.html.twig @@ -8,7 +8,7 @@ {% if activity is not null %}
      {% if is_granted('CHILL_ACTIVITY_SEE', activity) %} - {% include 'ChillActivityBundle:Activity:_list_item.html.twig' with { + {% include '@ChillActivity/Activity/_list_item.html.twig' with { 'recordAction': _self.recordAction(activity), 'context': 'accompanyingCourse', 'itemBlocClass': 'bg-chill-llight-gray' diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig index 43a8eb86b..39daee516 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig @@ -16,11 +16,11 @@ {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} -{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} +{% import '@ChillActivity/ActivityReason/macro.html.twig' as m %} {% block content -%}
      - {% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'person'} %} + {% include '@ChillActivity/Activity/show.html.twig' with {'context': 'person'} %}
      {% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Timeline/activity_person_context.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Timeline/activity_person_context.html.twig index 435be1958..3979be2f3 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Timeline/activity_person_context.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Timeline/activity_person_context.html.twig @@ -1,4 +1,4 @@ -{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} +{% import '@ChillActivity/ActivityReason/macro.html.twig' as m %}

      {{ activity.date|format_date('long') }} / {{ 'Activity'|trans }}{% if 'person' != context %} / {{ activity.person|chill_entity_render_box({'addLink': true}) }}{% endif %}

      diff --git a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStatsVoter.php b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStatsVoter.php index 2e55f862c..27f4b07a6 100644 --- a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStatsVoter.php +++ b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStatsVoter.php @@ -20,9 +20,9 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class ActivityStatsVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface { - public const LISTS = 'CHILL_ACTIVITY_LIST'; + final public const LISTS = 'CHILL_ACTIVITY_LIST'; - public const STATS = 'CHILL_ACTIVITY_STATS'; + final public const STATS = 'CHILL_ACTIVITY_STATS'; protected VoterHelperInterface $helper; diff --git a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php index 15d2441d5..bf2ed78a0 100644 --- a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php +++ b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php @@ -21,12 +21,9 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter; -use RuntimeException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Security; -use function in_array; - class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface { /** @@ -35,7 +32,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn * * It is safe for usage in template and controller */ - public const CREATE = 'CHILL_ACTIVITY_CREATE'; + final public const CREATE = 'CHILL_ACTIVITY_CREATE'; /** * role to allow to create an activity associated win an accompanying course. @@ -44,7 +41,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn * * @internal */ - public const CREATE_ACCOMPANYING_COURSE = 'CHILL_ACTIVITY_CREATE_ACCOMPANYING_COURSE'; + final public const CREATE_ACCOMPANYING_COURSE = 'CHILL_ACTIVITY_CREATE_ACCOMPANYING_COURSE'; /** * role to allow to create an activity associated with a person. @@ -53,17 +50,17 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn * * @internal */ - public const CREATE_PERSON = 'CHILL_ACTIVITY_CREATE_PERSON'; + final public const CREATE_PERSON = 'CHILL_ACTIVITY_CREATE_PERSON'; - public const DELETE = 'CHILL_ACTIVITY_DELETE'; + final public const DELETE = 'CHILL_ACTIVITY_DELETE'; - public const FULL = 'CHILL_ACTIVITY_FULL'; + final public const FULL = 'CHILL_ACTIVITY_FULL'; - public const SEE = 'CHILL_ACTIVITY_SEE'; + final public const SEE = 'CHILL_ACTIVITY_SEE'; - public const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS'; + final public const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS'; - public const UPDATE = 'CHILL_ACTIVITY_UPDATE'; + final public const UPDATE = 'CHILL_ACTIVITY_UPDATE'; private const ALL = [ self::CREATE, @@ -74,15 +71,12 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn self::FULL, ]; - protected Security $security; - protected VoterHelperInterface $voterHelper; public function __construct( - Security $security, + protected Security $security, VoterHelperFactoryInterface $voterHelperFactory ) { - $this->security = $security; $this->voterHelper = $voterHelperFactory->generate(self::class) ->addCheckFor(Person::class, [self::SEE, self::CREATE]) ->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE]) @@ -148,11 +142,11 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn return $this->voterHelper->voteOnAttribute(self::CREATE_ACCOMPANYING_COURSE, $subject->getAccompanyingPeriod(), $token); } } else { - throw new RuntimeException('Could not determine context of activity.'); + throw new \RuntimeException('Could not determine context of activity.'); } } elseif ($subject instanceof AccompanyingPeriod) { if (AccompanyingPeriod::STEP_CLOSED === $subject->getStep()) { - if (in_array($attribute, [self::UPDATE, self::CREATE, self::DELETE], true)) { + if (\in_array($attribute, [self::UPDATE, self::CREATE, self::DELETE], true)) { return false; } } diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php index a20ca365b..6c3675011 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php @@ -18,7 +18,6 @@ use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\DocGeneratorBundle\Service\Context\BaseContextData; use Chill\DocStoreBundle\Entity\StoredObject; -use Chill\DocStoreBundle\Repository\DocumentCategoryRepository; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; @@ -42,48 +41,17 @@ class ActivityContext implements DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface { - private BaseContextData $baseContextData; - - private DocumentCategoryRepository $documentCategoryRepository; - - private EntityManagerInterface $em; - - private NormalizerInterface $normalizer; - - private PersonRenderInterface $personRender; - - private PersonRepository $personRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; - - private TranslatorInterface $translator; - - private ThirdPartyRender $thirdPartyRender; - - private ThirdPartyRepository $thirdPartyRepository; - public function __construct( - DocumentCategoryRepository $documentCategoryRepository, - NormalizerInterface $normalizer, - TranslatableStringHelperInterface $translatableStringHelper, - EntityManagerInterface $em, - PersonRenderInterface $personRender, - PersonRepository $personRepository, - TranslatorInterface $translator, - BaseContextData $baseContextData, - ThirdPartyRender $thirdPartyRender, - ThirdPartyRepository $thirdPartyRepository + private readonly NormalizerInterface $normalizer, + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly EntityManagerInterface $em, + private readonly PersonRenderInterface $personRender, + private readonly PersonRepository $personRepository, + private readonly TranslatorInterface $translator, + private readonly BaseContextData $baseContextData, + private readonly ThirdPartyRender $thirdPartyRender, + private readonly ThirdPartyRepository $thirdPartyRepository ) { - $this->documentCategoryRepository = $documentCategoryRepository; - $this->normalizer = $normalizer; - $this->translatableStringHelper = $translatableStringHelper; - $this->em = $em; - $this->personRender = $personRender; - $this->personRepository = $personRepository; - $this->translator = $translator; - $this->baseContextData = $baseContextData; - $this->thirdPartyRender = $thirdPartyRender; - $this->thirdPartyRepository = $thirdPartyRepository; } public function adminFormReverseTransform(array $data): array @@ -159,7 +127,7 @@ class ActivityContext implements 'multiple' => false, 'required' => false, 'expanded' => true, - 'label' => $options[$key . 'Label'], + 'label' => $options[$key.'Label'], 'placeholder' => $this->translator->trans('Any person selected'), ]); } @@ -242,7 +210,7 @@ class ActivityContext implements if ($options['thirdParty']) { $data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'], 'docgen', [ 'docgen:expects' => ThirdParty::class, - 'groups' => 'docgen:read' + 'groups' => 'docgen:read', ]); } @@ -285,7 +253,7 @@ class ActivityContext implements { $options = $template->getOptions(); - return $options['mainPerson'] || $options['person1'] || $options['person2'] || $options ['thirdParty']; + return $options['mainPerson'] || $options['person1'] || $options['person2'] || $options['thirdParty']; } public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php index 045d09beb..baf1c279d 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Service\DocGenerator; +use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityPresence; use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface; @@ -32,13 +33,11 @@ use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Service\DocGenerator\AccompanyingPeriodContext; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; -use DateTime; use libphonenumber\PhoneNumber; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use function in_array; /** * @implements DocGeneratorContextWithPublicFormInterface @@ -48,44 +47,17 @@ class ListActivitiesByAccompanyingPeriodContext implements DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface { - private AccompanyingPeriodContext $accompanyingPeriodContext; - - private ActivityACLAwareRepositoryInterface $activityACLAwareRepository; - - private NormalizerInterface $normalizer; - - private PersonRepository $personRepository; - - private SocialActionRepository $socialActionRepository; - - private SocialIssueRepository $socialIssueRepository; - - private ThirdPartyRepository $thirdPartyRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; - - private UserRepository $userRepository; - public function __construct( - AccompanyingPeriodContext $accompanyingPeriodContext, - ActivityACLAwareRepositoryInterface $activityACLAwareRepository, - NormalizerInterface $normalizer, - PersonRepository $personRepository, - SocialActionRepository $socialActionRepository, - SocialIssueRepository $socialIssueRepository, - ThirdPartyRepository $thirdPartyRepository, - TranslatableStringHelperInterface $translatableStringHelper, - UserRepository $userRepository, + private readonly AccompanyingPeriodContext $accompanyingPeriodContext, + private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository, + private readonly NormalizerInterface $normalizer, + private readonly PersonRepository $personRepository, + private readonly SocialActionRepository $socialActionRepository, + private readonly SocialIssueRepository $socialIssueRepository, + private readonly ThirdPartyRepository $thirdPartyRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly UserRepository $userRepository ) { - $this->accompanyingPeriodContext = $accompanyingPeriodContext; - $this->activityACLAwareRepository = $activityACLAwareRepository; - $this->normalizer = $normalizer; - $this->personRepository = $personRepository; - $this->socialActionRepository = $socialActionRepository; - $this->socialIssueRepository = $socialIssueRepository; - $this->thirdPartyRepository = $thirdPartyRepository; - $this->translatableStringHelper = $translatableStringHelper; - $this->userRepository = $userRepository; } public function adminFormReverseTransform(array $data): array @@ -141,7 +113,7 @@ class ListActivitiesByAccompanyingPeriodContext implements } /** - * @return list + * @return list */ private function filterActivitiesByUser(array $activities, User $user): array { @@ -149,15 +121,22 @@ class ListActivitiesByAccompanyingPeriodContext implements array_filter( $activities, function ($activity) use ($user) { + $u = $activity['user']; + + if (null !== $u && $u['username'] === $user->getUsername()) { + return true; + } + $activityUsernames = array_map(static fn ($user) => $user['username'], $activity['users'] ?? []); - return in_array($user->getUsername(), $activityUsernames, true); + + return \in_array($user->getUsername(), $activityUsernames, true); } ) ); } /** - * @return list + * @return list */ private function filterWorksByUser(array $works, User $user): array { @@ -167,7 +146,7 @@ class ListActivitiesByAccompanyingPeriodContext implements function ($work) use ($user) { $workUsernames = array_map(static fn ($user) => $user['username'], $work['referrers'] ?? []); - return in_array($user->getUsername(), $workUsernames, true); + return \in_array($user->getUsername(), $workUsernames, true); } ) ); @@ -191,6 +170,7 @@ class ListActivitiesByAccompanyingPeriodContext implements if ($myWorksOnly && isset($contextGenerationData['creator'])) { $data['course']['works'] = $this->filterWorksByUser($data['course']['works'], $contextGenerationData['creator']); } + return $data; } @@ -243,8 +223,17 @@ class ListActivitiesByAccompanyingPeriodContext implements foreach ($activities as $row) { $activity = $row[0]; + $user = match (null === $row['userId']) { + false => $this->userRepository->find($row['userId']), + true => null, + }; + + $activity['user'] = $this->normalizer->normalize($user, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class, + ]); + $activity['date'] = $this->normalizer->normalize($activity['date'], 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => DateTime::class, + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => \DateTime::class, ]); if (null === $activity['location']) { @@ -257,8 +246,8 @@ class ListActivitiesByAccompanyingPeriodContext implements $activity['location']['type'] = 'location'; foreach (['1', '2'] as $key) { - $activity['location']['phonenumber' . $key] = $this->normalizer->normalize( - $activity['location']['phonenumber' . $key], + $activity['location']['phonenumber'.$key] = $this->normalizer->normalize( + $activity['location']['phonenumber'.$key], 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => PhoneNumber::class] ); diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php index 334b5d2df..291ef5832 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php @@ -22,13 +22,11 @@ use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; -use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\MappingException; use Symfony\Component\Security\Core\Security; -final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface +final readonly class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface { public const KEY = 'accompanying_period_activity_document'; @@ -39,7 +37,7 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo ) { } - public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); $activityMetadata = $this->em->getClassMetadata(Activity::class); @@ -84,7 +82,7 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo if (null !== $content) { $query->addWhereClause( 'doc_obj.title ilike ?', - ['%' . $content . '%'], + ['%'.$content.'%'], [Types::STRING] ); } @@ -92,10 +90,6 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo return $query; } - /** - * @param AccompanyingPeriod $accompanyingPeriod - * @return bool - */ public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool { return $this->security->isGranted(ActivityVoter::SEE, $accompanyingPeriod); @@ -106,7 +100,7 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); } - public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + public function buildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { return $this->activityDocumentACLAwareRepository ->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content); diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php index cf96449ab..618775452 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php @@ -11,15 +11,11 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Service\GenericDoc\Providers; -use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepository; use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; -use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface; use Chill\PersonBundle\Entity\Person; -use DateTimeImmutable; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Security; final readonly class PersonActivityGenericDocProvider implements GenericDocForPersonProviderInterface @@ -27,12 +23,12 @@ final readonly class PersonActivityGenericDocProvider implements GenericDocForPe public const KEY = 'person_activity_document'; public function __construct( - private Security $security, + private Security $security, private ActivityDocumentACLAwareRepositoryInterface $personActivityDocumentACLAwareRepository, ) { } - public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + public function buildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { return $this->personActivityDocumentACLAwareRepository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext( $person, @@ -42,10 +38,6 @@ final readonly class PersonActivityGenericDocProvider implements GenericDocForPe ); } - /** - * @param Person $person - * @return bool - */ public function isAllowedForPerson(Person $person): bool { return $this->security->isGranted(ActivityVoter::SEE, $person); diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php index c465dbca1..76f0fc00d 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -17,23 +17,16 @@ use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocPr use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; use Chill\DocStoreBundle\Repository\StoredObjectRepository; -use Chill\PersonBundle\Entity\AccompanyingPeriod; -final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface +final readonly class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface { - private StoredObjectRepository $objectRepository; - - private ActivityRepository $activityRepository; - - public function __construct(StoredObjectRepository $storedObjectRepository, ActivityRepository $activityRepository) + public function __construct(private StoredObjectRepository $objectRepository, private ActivityRepository $activityRepository) { - $this->objectRepository = $storedObjectRepository; - $this->activityRepository = $activityRepository; } public function supports(GenericDocDTO $genericDocDTO, $options = []): bool { - return $genericDocDTO->key === AccompanyingPeriodActivityGenericDocProvider::KEY || $genericDocDTO->key === PersonActivityGenericDocProvider::KEY; + return AccompanyingPeriodActivityGenericDocProvider::KEY === $genericDocDTO->key || PersonActivityGenericDocProvider::KEY === $genericDocDTO->key; } public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string diff --git a/src/Bundle/ChillActivityBundle/Templating/Entity/ActivityReasonRender.php b/src/Bundle/ChillActivityBundle/Templating/Entity/ActivityReasonRender.php index c60a8d286..aa7783d2d 100644 --- a/src/Bundle/ChillActivityBundle/Templating/Entity/ActivityReasonRender.php +++ b/src/Bundle/ChillActivityBundle/Templating/Entity/ActivityReasonRender.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Templating\Entity; use Chill\ActivityBundle\Entity\ActivityReason; -use Chill\MainBundle\Templating\Entity\AbstractChillEntityRender; use Chill\MainBundle\Templating\Entity\BoxUtilsChillEntityRenderTrait; use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -38,21 +37,21 @@ class ActivityReasonRender implements ChillEntityRenderInterface public function renderBox($entity, array $options): string { return - $this->getDefaultOpeningBox('activity-reason') . - '' . - ' ' . - '' . + $this->getDefaultOpeningBox('activity-reason'). + ''. + ' '. + ''. $this->translatableStringHelper->localize( $entity->getCategory()->getName() - ) . - '' . - ' > ' . - '' . + ). + ''. + ' > '. + ''. $this->translatableStringHelper->localize( $entity->getName() - ) . - '' . - '' . + ). + ''. + ''. $this->getDefaultClosingBox(); } @@ -63,10 +62,10 @@ class ActivityReasonRender implements ChillEntityRenderInterface if (null !== $entity->getCategory()) { $category = $this->translatableStringHelper->localize( $entity->getCategory()->getName() - ) . ' > '; + ).' > '; } - return $category . + return $category. $this->translatableStringHelper->localize( $entity->getName() ); diff --git a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityControllerTest.php b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityControllerTest.php index 04301afae..31012569a 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityControllerTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityControllerTest.php @@ -12,14 +12,12 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Controller; use Chill\ActivityBundle\Entity\ActivityType; -use RuntimeException; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Security\Core\Role\Role; -use function count; -use function in_array; /** * @internal + * * @coversNothing */ final class ActivityControllerTest extends WebTestCase @@ -76,10 +74,8 @@ final class ActivityControllerTest extends WebTestCase /** * @dataProvider getSecuredPagesUnauthenticated - * - * @param mixed $url */ - public function testAccessIsDeniedForUnauthenticated($url) + public function testAccessIsDeniedForUnauthenticated(mixed $url) { $client = $this->createClient(); @@ -194,7 +190,7 @@ final class ActivityControllerTest extends WebTestCase $crawler = $client->followRedirect(); - $this->assertNotContains('January 25, 2015', $crawler->text()); + $this->assertStringNotContainsString('January 25, 2015', $crawler->text()); } /** @@ -208,19 +204,19 @@ final class ActivityControllerTest extends WebTestCase $container = self::$kernel->getContainer(); $em = $container->get('doctrine.orm.entity_manager'); - //get the social PermissionGroup, and remove CHILL_ACTIVITY_* + // get the social PermissionGroup, and remove CHILL_ACTIVITY_* $socialPermissionGroup = $em ->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class) ->findOneByName('social'); $withoutActivityPermissionGroup = (new \Chill\MainBundle\Entity\PermissionsGroup()) ->setName('social without activity'); - //copy role scopes where ACTIVITY is not present + // copy role scopes where ACTIVITY is not present foreach ($socialPermissionGroup->getRoleScopes() as $roleScope) { - if (!strpos($roleScope->getRole(), 'ACTIVITY')) { + if (!strpos((string) $roleScope->getRole(), 'ACTIVITY')) { $withoutActivityPermissionGroup->addRoleScope($roleScope); } } - //create groupCenter + // create groupCenter $groupCenter = new \Chill\MainBundle\Entity\GroupCenter(); $groupCenter->setCenter($em->getRepository(\Chill\MainBundle\Entity\Center::class) ->findOneBy(['name' => 'Center A'])) @@ -228,7 +224,7 @@ final class ActivityControllerTest extends WebTestCase $em->persist($withoutActivityPermissionGroup); $em->persist($groupCenter); - //create user + // create user $faker = \Faker\Factory::create(); $username = $faker->name; $user = new \Chill\MainBundle\Entity\User(); @@ -253,20 +249,17 @@ final class ActivityControllerTest extends WebTestCase $activities = $em->getRepository(\Chill\ActivityBundle\Entity\Activity::class) ->findBy(['person' => $person]); - if (count($activities) === 0) { - throw new RuntimeException('We need activities associated with this ' - . 'person. Did you forget to add fixtures ?'); + if (0 === \count($activities)) { + throw new \RuntimeException('We need activities associated with this person. Did you forget to add fixtures ?'); } return $activities; } /** - * @param mixed $username - * - * @return \Symfony\Component\BrowserKit\Client + * @return \Symfony\Component\BrowserKit\AbstractBrowser */ - private function getAuthenticatedClient($username = 'center a_social') + private function getAuthenticatedClient(mixed $username = 'center a_social') { return self::createClient([], [ 'PHP_AUTH_USER' => $username, @@ -289,8 +282,7 @@ final class ActivityControllerTest extends WebTestCase ]); if (null === $person) { - throw new RuntimeException('We need a person with firstname Gérard and' - . ' lastname Depardieu. Did you add fixtures ?'); + throw new \RuntimeException('We need a person with firstname Gérard and lastname Depardieu. Did you add fixtures ?'); } return $person; @@ -310,7 +302,7 @@ final class ActivityControllerTest extends WebTestCase $reason = $reasons[array_rand($reasons)]; - if (in_array($reason->getId(), $excludeIds, true)) { + if (\in_array($reason->getId(), $excludeIds, true)) { return $this->getRandomActivityReason($excludeIds); } @@ -318,7 +310,7 @@ final class ActivityControllerTest extends WebTestCase } /** - * @return \Chill\ActivityBundle\Entity\ActivityType + * @return ActivityType */ private function getRandomActivityType() { @@ -344,8 +336,7 @@ final class ActivityControllerTest extends WebTestCase ->findOneByUsername($username); if (null === $user) { - throw new RuntimeException("The user with username {$username} " - . 'does not exists in database. Did you add fixtures ?'); + throw new \RuntimeException("The user with username {$username} ".'does not exists in database. Did you add fixtures ?'); } $center = self::$kernel->getContainer() @@ -358,14 +349,14 @@ final class ActivityControllerTest extends WebTestCase ->get('chill.main.security.authorization.helper') ->getReachableScopes( $user, - new Role('CHILL_ACTIVITY_UPDATE'), + 'CHILL_ACTIVITY_UPDATE', $center ); $reachableScopesDelete = self::$kernel->getContainer() ->get('chill.main.security.authorization.helper') ->getReachableScopes( $user, - new Role('CHILL_ACTIVITY_DELETE'), + 'CHILL_ACTIVITY_DELETE', $center ); $reachableScopesId = array_intersect( @@ -373,13 +364,12 @@ final class ActivityControllerTest extends WebTestCase array_map(static fn ($s) => $s->getId(), $reachableScopesUpdate) ); - if (count($reachableScopesId) === 0) { - throw new RuntimeException('there are not scope reachable for ' - . 'both CHILL_ACTIVITY_UPDATE and CHILL_ACTIVITY_DELETE'); + if (0 === \count($reachableScopesId)) { + throw new \RuntimeException('there are not scope reachable for both CHILL_ACTIVITY_UPDATE and CHILL_ACTIVITY_DELETE'); } foreach ($reachableScopesUpdate as $scope) { - if (in_array($scope->getId(), $reachableScopesId, true)) { + if (\in_array($scope->getId(), $reachableScopesId, true)) { $reachableScopes[] = $scope; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonCategoryControllerTest.php b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonCategoryControllerTest.php index 14f75e863..81378b5ef 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonCategoryControllerTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonCategoryControllerTest.php @@ -15,11 +15,15 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** * @internal + * * @coversNothing */ final class ActivityReasonCategoryControllerTest extends WebTestCase { - public function testToWrite() + /** + * @doesNotPerformAssertions + */ + public function testToWrite(): never { $this->markTestSkipped(); } diff --git a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonControllerTest.php b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonControllerTest.php index 47de677a3..ce37d3fb7 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonControllerTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonControllerTest.php @@ -15,11 +15,15 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** * @internal + * * @coversNothing */ final class ActivityReasonControllerTest extends WebTestCase { - public function testToWrite() + /** + * @doesNotPerformAssertions + */ + public function testToWrite(): never { $this->markTestSkipped(); } diff --git a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityTypeControllerTest.php b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityTypeControllerTest.php index b2276f427..23de2d17c 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityTypeControllerTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityTypeControllerTest.php @@ -15,11 +15,15 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** * @internal + * * @coversNothing */ final class ActivityTypeControllerTest extends WebTestCase { - public function testToWrite() + /** + * @doesNotPerformAssertions + */ + public function testToWrite(): never { $this->markTestSkipped(); } diff --git a/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php index 3d91c3da9..6610b64ff 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php @@ -22,6 +22,7 @@ use Prophecy\PhpUnit\ProphecyTrait; /** * @internal + * * @coversNothing */ final class ActivityTest extends TestCase diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByActivityTypeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByActivityTypeAggregatorTest.php new file mode 100644 index 000000000..2d78585f7 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByActivityTypeAggregatorTest.php @@ -0,0 +1,87 @@ +rollingDateConverter = self::$container->get(RollingDateConverterInterface::class); + $this->activityTypeRepository = self::$container->get(ActivityTypeRepositoryInterface::class); + $this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class); + } + + public function getAggregator() + { + return new ByActivityTypeAggregator( + $this->rollingDateConverter, + $this->activityTypeRepository, + $this->translatableStringHelper, + ); + } + + public function getFormData() + { + return [ + [ + 'after_date' => null, + 'before_date' => null, + ], + [ + 'after_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'before_date' => null, + ], + [ + 'after_date' => null, + 'before_date' => new RollingDate(RollingDate::T_TODAY), + ], + [ + 'after_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'before_date' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(distinct acp.id)') + ->from(AccompanyingPeriod::class, 'acp'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialActionAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialActionAggregatorTest.php index 7f6c76332..6cb0eb299 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialActionAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialActionAggregatorTest.php @@ -18,6 +18,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class BySocialActionAggregatorTest extends AbstractAggregatorTest @@ -45,9 +46,7 @@ final class BySocialActionAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); @@ -55,8 +54,8 @@ final class BySocialActionAggregatorTest extends AbstractAggregatorTest $em->createQueryBuilder() ->select('count(activity.id)') ->from(Activity::class, 'activity') - ->join('activity.accompanyingPeriod', 'acp') - ->join('activity.socialActions', 'actsocialaction'), + ->leftJoin('activity.accompanyingPeriod', 'acp') + ->leftJoin('activity.socialActions', 'actsocialaction'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialIssueAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialIssueAggregatorTest.php index c8374d370..583e950a6 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialIssueAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialIssueAggregatorTest.php @@ -18,6 +18,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class BySocialIssueAggregatorTest extends AbstractAggregatorTest @@ -45,18 +46,16 @@ final class BySocialIssueAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() - ->select('count(activity.id)') + ->select('count(activity)') ->from(Activity::class, 'activity') - ->join('activity.accompanyingPeriod', 'acp') - ->join('activity.socialIssues', 'actsocialissue'), + ->leftJoin('activity.accompanyingPeriod', 'acp') + ->leftJoin('activity.socialIssues', 'actsocialissue'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityPresenceAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityPresenceAggregatorTest.php new file mode 100644 index 000000000..ea2bafb0d --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityPresenceAggregatorTest.php @@ -0,0 +1,62 @@ +translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class); + $this->activityPresenceRepository = self::$container->get(ActivityPresenceRepositoryInterface::class); + } + + public function getAggregator() + { + return new ActivityPresenceAggregator($this->activityPresenceRepository, $this->translatableStringHelper); + } + + public function getFormData() + { + return [ + [], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php similarity index 58% rename from src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php index 0a61e983f..3e1d473f0 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php @@ -9,15 +9,17 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Aggregator\PersonAggregators; +namespace Chill\ActivityBundle\Tests\Export\Aggregator; -use Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator; +use Chill\ActivityBundle\Entity\Activity; +use Chill\ActivityBundle\Export\Aggregator\ActivityReasonAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; use Prophecy\PhpUnit\ProphecyTrait; /** * @internal + * * @coversNothing */ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest @@ -30,15 +32,15 @@ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest { self::bootKernel(); - $this->aggregator = self::$container->get('chill.activity.export.reason_aggregator'); + $this->aggregator = self::$container->get(ActivityReasonAggregator::class); + /* + $request = $this->prophesize() + ->willExtend(\Symfony\Component\HttpFoundation\Request::class); - $request = $this->prophesize() - ->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); - $request->getLocale()->willReturn('fr'); - - self::$container->get('request_stack') - ->push($request->reveal()); + self::$container->get('request_stack') + ->push($request->reveal());*/ } public function getAggregator() @@ -56,23 +58,26 @@ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity'), + ->from(Activity::class, 'activity') + ->join('activity.person', 'person'), $em->createQueryBuilder() ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'accompanyingPeriod'), + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') ->join('activity.reasons', 'actreasons'), $em->createQueryBuilder() ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity') + ->from(Activity::class, 'activity') ->join('activity.reasons', 'actreasons') ->join('actreasons.category', 'actreasoncat'), ]; diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php index 31633b9a3..bb0906be6 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Aggregator; +use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Aggregator\ActivityTypeAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; @@ -20,6 +21,7 @@ use Prophecy\PhpUnit\ProphecyTrait; * Add tests for ActivityTypeAggregator. * * @internal + * * @coversNothing */ final class ActivityTypeAggregatorTest extends AbstractAggregatorTest @@ -32,7 +34,7 @@ final class ActivityTypeAggregatorTest extends AbstractAggregatorTest { self::bootKernel(); - $this->aggregator = self::$container->get('chill.activity.export.type_aggregator'); + $this->aggregator = self::$container->get(ActivityTypeAggregator::class); $request = $this->prophesize() ->willExtend(\Symfony\Component\HttpFoundation\Request::class); @@ -57,19 +59,17 @@ final class ActivityTypeAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders() { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity'), + ->from(Activity::class, 'activity'), $em->createQueryBuilder() ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity') + ->from(Activity::class, 'activity') ->join('activity.activityType', 'acttype'), ]; } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityUserAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityUserAggregatorTest.php index 06542aa41..0cd7fe14d 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityUserAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityUserAggregatorTest.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Aggregator; +use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; @@ -20,6 +21,7 @@ use Prophecy\PhpUnit\ProphecyTrait; * Add tests for ActivityUsernAggregator. * * @internal + * * @coversNothing */ final class ActivityUserAggregatorTest extends AbstractAggregatorTest @@ -57,16 +59,14 @@ final class ActivityUserAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity'), + ->from(Activity::class, 'activity'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByThirdpartyAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ByThirdpartyAggregatorTest.php similarity index 70% rename from src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByThirdpartyAggregatorTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ByThirdpartyAggregatorTest.php index e3bbde25f..0558c3f2b 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByThirdpartyAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ByThirdpartyAggregatorTest.php @@ -9,15 +9,16 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Tests\Export\Aggregator; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator; +use Chill\ActivityBundle\Export\Aggregator\ByThirdpartyAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class ByThirdpartyAggregatorTest extends AbstractAggregatorTest @@ -28,7 +29,7 @@ final class ByThirdpartyAggregatorTest extends AbstractAggregatorTest { self::bootKernel(); - $this->aggregator = self::$container->get('chill.activity.export.bythirdparty_aggregator'); + $this->aggregator = self::$container->get(ByThirdpartyAggregator::class); } public function getAggregator() @@ -45,9 +46,7 @@ final class ByThirdpartyAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); @@ -55,8 +54,8 @@ final class ByThirdpartyAggregatorTest extends AbstractAggregatorTest $em->createQueryBuilder() ->select('count(activity.id)') ->from(Activity::class, 'activity') - ->join('activity.accompanyingPeriod', 'acp') - ->join('activity.thirdParties', 'acttparty'), + ->leftJoin('activity.accompanyingPeriod', 'acp') + ->leftJoin('activity.thirdParties', 'acttparty'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ByUserAggregatorTest.php similarity index 70% rename from src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ByUserAggregatorTest.php index ff4f42ec4..3e8359ab8 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ByUserAggregatorTest.php @@ -9,15 +9,16 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Tests\Export\Aggregator; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByCreatorAggregator; +use Chill\ActivityBundle\Export\Aggregator\ByCreatorAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class ByUserAggregatorTest extends AbstractAggregatorTest @@ -28,7 +29,7 @@ final class ByUserAggregatorTest extends AbstractAggregatorTest { self::bootKernel(); - $this->aggregator = self::$container->get('chill.activity.export.byuser_aggregator'); + $this->aggregator = self::$container->get(ByCreatorAggregator::class); } public function getAggregator() @@ -45,9 +46,7 @@ final class ByUserAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); @@ -55,8 +54,8 @@ final class ByUserAggregatorTest extends AbstractAggregatorTest $em->createQueryBuilder() ->select('count(activity.id)') ->from(Activity::class, 'activity') - ->join('activity.accompanyingPeriod', 'acp') - ->join('activity.users', 'actusers'), + ->leftJoin('activity.accompanyingPeriod', 'acp') + ->leftJoin('activity.users', 'actusers'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/CreatorJobAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/CreatorJobAggregatorTest.php new file mode 100644 index 000000000..9a821de75 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/CreatorJobAggregatorTest.php @@ -0,0 +1,61 @@ +aggregator = self::$container->get(CreatorJobAggregator::class); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->leftJoin('activity.accompanyingPeriod', 'acp') + ->leftJoin('activity.user', 'actuser'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/CreatorScopeAggregatorTest.php similarity index 66% rename from src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/CreatorScopeAggregatorTest.php index 1265804f9..49c1d746f 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/CreatorScopeAggregatorTest.php @@ -9,18 +9,19 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Tests\Export\Aggregator; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\CreatorScopeAggregator; +use Chill\ActivityBundle\Export\Aggregator\CreatorScopeAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ -final class UserScopeAggregatorTest extends AbstractAggregatorTest +final class CreatorScopeAggregatorTest extends AbstractAggregatorTest { private CreatorScopeAggregator $aggregator; @@ -28,7 +29,7 @@ final class UserScopeAggregatorTest extends AbstractAggregatorTest { self::bootKernel(); - $this->aggregator = self::$container->get('chill.activity.export.userscope_aggregator'); + $this->aggregator = self::$container->get(CreatorScopeAggregator::class); } public function getAggregator() @@ -45,9 +46,7 @@ final class UserScopeAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); @@ -55,8 +54,8 @@ final class UserScopeAggregatorTest extends AbstractAggregatorTest $em->createQueryBuilder() ->select('count(activity.id)') ->from(Activity::class, 'activity') - ->join('activity.accompanyingPeriod', 'acp') - ->join('activity.user', 'actuser'), + ->leftJoin('activity.accompanyingPeriod', 'acp') + ->leftJoin('activity.user', 'actuser'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/DateAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/DateAggregatorTest.php similarity index 76% rename from src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/DateAggregatorTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/DateAggregatorTest.php index feb5ed970..db3a3ff76 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/DateAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/DateAggregatorTest.php @@ -9,15 +9,16 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Tests\Export\Aggregator; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\DateAggregator; +use Chill\ActivityBundle\Export\Aggregator\DateAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class DateAggregatorTest extends AbstractAggregatorTest @@ -28,7 +29,7 @@ final class DateAggregatorTest extends AbstractAggregatorTest { self::bootKernel(); - $this->aggregator = self::$container->get('chill.activity.export.date_aggregator'); + $this->aggregator = self::$container->get(DateAggregator::class); } public function getAggregator() @@ -53,9 +54,7 @@ final class DateAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); @@ -63,7 +62,7 @@ final class DateAggregatorTest extends AbstractAggregatorTest $em->createQueryBuilder() ->select('count(activity.id)') ->from(Activity::class, 'activity') - ->join('activity.accompanyingPeriod', 'acp'), + ->leftJoin('activity.accompanyingPeriod', 'acp'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/LocationTypeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php similarity index 70% rename from src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/LocationTypeAggregatorTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php index c384eb772..663901edd 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/LocationTypeAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php @@ -9,15 +9,16 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators; +namespace Chill\ActivityBundle\Tests\Export\Aggregator; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\LocationTypeAggregator; +use Chill\ActivityBundle\Export\Aggregator\LocationTypeAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class LocationTypeAggregatorTest extends AbstractAggregatorTest @@ -28,7 +29,7 @@ final class LocationTypeAggregatorTest extends AbstractAggregatorTest { self::bootKernel(); - $this->aggregator = self::$container->get('chill.activity.export.locationtype_aggregator'); + $this->aggregator = self::$container->get(LocationTypeAggregator::class); } public function getAggregator() @@ -45,9 +46,7 @@ final class LocationTypeAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); @@ -55,8 +54,8 @@ final class LocationTypeAggregatorTest extends AbstractAggregatorTest $em->createQueryBuilder() ->select('count(activity.id)') ->from(Activity::class, 'activity') - ->join('activity.accompanyingPeriod', 'acp') - ->join('activity.location', 'actloc'), + ->leftJoin('activity.accompanyingPeriod', 'acp') + ->leftJoin('activity.location', 'actloc'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/PersonAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/PersonAggregatorTest.php new file mode 100644 index 000000000..388f13793 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/PersonAggregatorTest.php @@ -0,0 +1,57 @@ +labelPersonHelper = self::$container->get(LabelPersonHelper::class); + } + + public function getAggregator() + { + return new PersonAggregator($this->labelPersonHelper); + } + + public function getFormData() + { + return [[]]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonsAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonsAggregatorTest.php new file mode 100644 index 000000000..b067e308d --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonsAggregatorTest.php @@ -0,0 +1,60 @@ +labelPersonHelper = self::$container->get(LabelPersonHelper::class); + } + + public function getAggregator() + { + return new PersonsAggregator($this->labelPersonHelper); + } + + public function getFormData() + { + return [ + [], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityDurationTest.php index 2eb2176b0..c8010c327 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityDurationTest.php @@ -12,26 +12,27 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP; use Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityDuration; +use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\MainBundle\Test\Export\AbstractExportTest; /** * @internal + * * @coversNothing */ final class AvgActivityDurationTest extends AbstractExportTest { - private AvgActivityDuration $export; - protected function setUp(): void { self::bootKernel(); - - $this->export = self::$container->get('chill.activity.export.avg_activity_duration_linked_to_acp'); } public function getExport() { - return $this->export; + $activityRepository = self::$container->get(ActivityRepository::class); + + yield new AvgActivityDuration($activityRepository, $this->getParameters(true)); + yield new AvgActivityDuration($activityRepository, $this->getParameters(false)); } public function getFormData(): array diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php index 59b4384cf..414e72960 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php @@ -13,25 +13,26 @@ namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP; use Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityVisitDuration; use Chill\MainBundle\Test\Export\AbstractExportTest; +use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class AvgActivityVisitDurationTest extends AbstractExportTest { - private AvgActivityVisitDuration $export; - protected function setUp(): void { self::bootKernel(); - - $this->export = self::$container->get('chill.activity.export.avg_activity_visit_duration_linked_to_acp'); } public function getExport() { - return $this->export; + $em = self::$container->get(EntityManagerInterface::class); + + yield new AvgActivityVisitDuration($em, $this->getParameters(true)); + yield new AvgActivityVisitDuration($em, $this->getParameters(false)); } public function getFormData(): array diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php index f03bbb956..174bcc790 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php @@ -13,25 +13,26 @@ namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP; use Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity; use Chill\MainBundle\Test\Export\AbstractExportTest; +use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class CountActivityTest extends AbstractExportTest { - private CountActivity $export; - protected function setUp(): void { self::bootKernel(); - - $this->export = self::$container->get('chill.activity.export.count_activity_linked_to_acp'); } public function getExport() { - return $this->export; + $em = self::$container->get(EntityManagerInterface::class); + + yield new CountActivity($em, $this->getParameters(true)); + yield new CountActivity($em, $this->getParameters(false)); } public function getFormData(): array diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountHouseholdOnActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountHouseholdOnActivityTest.php new file mode 100644 index 000000000..132813176 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountHouseholdOnActivityTest.php @@ -0,0 +1,60 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + } + + public function getExport() + { + yield new CountHouseholdOnActivity($this->entityManager, $this->getParameters(true)); + yield new CountHouseholdOnActivity($this->entityManager, $this->getParameters(false)); + } + + public function getFormData() + { + return [ + [], + ]; + } + + public function getModifiersCombination() + { + return [ + [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + PersonDeclarations::PERSON_TYPE, + PersonDeclarations::HOUSEHOLD_TYPE, + ], + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountPersonsOnActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountPersonsOnActivityTest.php new file mode 100644 index 000000000..25d6643fb --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountPersonsOnActivityTest.php @@ -0,0 +1,54 @@ +get(EntityManagerInterface::class); + + yield new CountPersonsOnActivity($em, $this->getParameters(true)); + yield new CountPersonsOnActivity($em, $this->getParameters(false)); + } + + public function getFormData() + { + return [[]]; + } + + public function getModifiersCombination() + { + return [[ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + PersonDeclarations::PERSON_TYPE, + ]]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php index 24a5133b6..f538ad3ea 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php @@ -13,25 +13,26 @@ namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP; use Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityDuration; use Chill\MainBundle\Test\Export\AbstractExportTest; +use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class SumActivityDurationTest extends AbstractExportTest { - private SumActivityDuration $export; - protected function setUp(): void { self::bootKernel(); - - $this->export = self::$container->get('chill.activity.export.sum_activity_duration_linked_to_acp'); } public function getExport() { - return $this->export; + $em = self::$container->get(EntityManagerInterface::class); + + yield new SumActivityDuration($em, $this->getParameters(true)); + yield new SumActivityDuration($em, $this->getParameters(false)); } public function getFormData(): array diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php index 1940140ac..95d4b597f 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php @@ -13,9 +13,11 @@ namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP; use Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityVisitDuration; use Chill\MainBundle\Test\Export\AbstractExportTest; +use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class SumActivityVisitDurationTest extends AbstractExportTest @@ -31,7 +33,10 @@ final class SumActivityVisitDurationTest extends AbstractExportTest public function getExport() { - return $this->export; + $em = self::$container->get(EntityManagerInterface::class); + + yield new SumActivityVisitDuration($em, $this->getParameters(true)); + yield new SumActivityVisitDuration($em, $this->getParameters(false)); } public function getFormData(): array diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php index 864ff55ba..8759dceb3 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php @@ -12,26 +12,27 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson; use Chill\ActivityBundle\Export\Export\LinkedToPerson\CountActivity; +use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\MainBundle\Test\Export\AbstractExportTest; /** * @internal + * * @coversNothing */ final class CountActivityTest extends AbstractExportTest { - private CountActivity $export; - protected function setUp(): void { self::bootKernel(); - - $this->export = self::$container->get('chill.activity.export.count_activity_linked_to_person'); } public function getExport() { - return $this->export; + $activityRepository = self::$container->get(ActivityRepository::class); + + yield new CountActivity($activityRepository, $this->getParameters(true)); + yield new CountActivity($activityRepository, $this->getParameters(false)); } public function getFormData(): array diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountHouseholdOnActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountHouseholdOnActivityTest.php new file mode 100644 index 000000000..65fdc6c4c --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountHouseholdOnActivityTest.php @@ -0,0 +1,59 @@ +activityRepository = self::$container->get(ActivityRepository::class); + } + + public function getExport() + { + yield new CountHouseholdOnActivity($this->activityRepository, $this->getParameters(true)); + yield new CountHouseholdOnActivity($this->activityRepository, $this->getParameters(false)); + } + + public function getFormData() + { + return [ + [], + ]; + } + + public function getModifiersCombination() + { + return [ + [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_PERSON, + PersonDeclarations::PERSON_TYPE, + PersonDeclarations::HOUSEHOLD_TYPE, + ], + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php index afa3da111..a6b6903f2 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php @@ -12,25 +12,28 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson; use Chill\ActivityBundle\Export\Export\LinkedToPerson\ListActivity; +use Chill\ActivityBundle\Repository\ActivityRepository; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Test\Export\AbstractExportTest; +use Doctrine\ORM\EntityManagerInterface; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Contracts\Translation\TranslatorInterface; /** * @internal + * * @coversNothing */ final class ListActivityTest extends AbstractExportTest { use ProphecyTrait; - private ListActivity $export; + private readonly ListActivity $export; protected function setUp(): void { self::bootKernel(); - $this->export = self::$container->get('chill.activity.export.list_activity_linked_to_person'); - $request = $this->prophesize() ->willExtend(\Symfony\Component\HttpFoundation\Request::class); @@ -42,7 +45,26 @@ final class ListActivityTest extends AbstractExportTest public function getExport() { - return $this->export; + $em = self::$container->get(EntityManagerInterface::class); + $translator = self::$container->get(TranslatorInterface::class); + $translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class); + $activityRepository = self::$container->get(ActivityRepository::class); + + yield new ListActivity( + $em, + $translator, + $translatableStringHelper, + $activityRepository, + $this->getParameters(true) + ); + + yield new ListActivity( + $em, + $translator, + $translatableStringHelper, + $activityRepository, + $this->getParameters(false) + ); } public function getFormData() diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php index 44d327ffd..8f4e98452 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php @@ -12,28 +12,30 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson; use Chill\ActivityBundle\Export\Export\LinkedToPerson\StatActivityDuration; +use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\MainBundle\Test\Export\AbstractExportTest; /** * Test the "sum" part of StatActivityDuration. * * @internal + * * @coversNothing */ final class StatActivityDurationTest extends AbstractExportTest { - private StatActivityDuration $export; + private readonly StatActivityDuration $export; protected function setUp(): void { self::bootKernel(); - - $this->export = self::$container->get('chill.activity.export.sum_activity_duration_linked_to_person'); } public function getExport() { - return $this->export; + $activityRepository = self::$container->get(ActivityRepository::class); + yield new StatActivityDuration($activityRepository, $this->getParameters(true), 'sum'); + yield new StatActivityDuration($activityRepository, $this->getParameters(false), 'sum'); } public function getFormData(): array diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php index 72b99375b..3701459da 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php @@ -13,25 +13,27 @@ namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityType; -use Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter; +use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\Expr; /** * @internal + * * @coversNothing */ final class ActivityTypeFilterTest extends AbstractFilterTest { - private ActivityTypeFilter $filter; + private \Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter $filter; protected function setUp(): void { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.filter_activitytype'); + $this->filter = self::$container->get(\Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter::class); } public function getFilter() @@ -41,19 +43,43 @@ final class ActivityTypeFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(ActivityType::class, 'at') ->select('at') ->getQuery() + ->setMaxResults(1) ->getResult(); $data = []; foreach ($array as $a) { $data[] = [ - 'accepted_activitytypes' => $a, + 'accepted_activitytypes' => [], + 'date_after' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'date_before' => new RollingDate(RollingDate::T_TODAY), + ]; + $data[] = [ + 'accepted_activitytypes' => new ArrayCollection([$a]), + 'date_after' => null, + 'date_before' => null, + ]; + $data[] = [ + 'accepted_activitytypes' => [$a], + 'date_after' => null, + 'date_before' => null, + ]; + $data[] = [ + 'accepted_activitytypes' => [$a], + 'date_after' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'date_before' => new RollingDate(RollingDate::T_TODAY), + ]; + $data[] = [ + 'accepted_activitytypes' => [], + 'date_after' => null, + 'date_before' => null, ]; } @@ -62,9 +88,7 @@ final class ActivityTypeFilterTest extends AbstractFilterTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByCreatorFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByCreatorFilterTest.php new file mode 100644 index 000000000..0b0c1f029 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByCreatorFilterTest.php @@ -0,0 +1,77 @@ +filter = self::$container->get(ByCreatorFilter::class); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(User::class, 'u') + ->select('u') + ->getQuery() + ->setMaxResults(2) + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_users' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.users', 'actusers'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialActionFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialActionFilterTest.php index a707e1242..e6ec47666 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialActionFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialActionFilterTest.php @@ -12,24 +12,25 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\PersonBundle\Entity\SocialWork\SocialAction; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class BySocialActionFilterTest extends AbstractFilterTest { - private BySocialActionFilter $filter; + private \Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter $filter; protected function setUp(): void { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.bysocialaction_filter'); + $this->filter = self::$container->get(\Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter::class); } public function getFilter() @@ -39,6 +40,8 @@ final class BySocialActionFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() @@ -51,7 +54,7 @@ final class BySocialActionFilterTest extends AbstractFilterTest foreach ($array as $a) { $data[] = [ - 'accepted_socialactions' => $a, + 'accepted_socialactions' => new ArrayCollection([$a]), ]; } @@ -60,9 +63,7 @@ final class BySocialActionFilterTest extends AbstractFilterTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialIssueFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialIssueFilterTest.php index 5f34a8f3b..47f45ceb5 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialIssueFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialIssueFilterTest.php @@ -15,10 +15,12 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialIssueFilter; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class BySocialIssueFilterTest extends AbstractFilterTest @@ -29,7 +31,7 @@ final class BySocialIssueFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.bysocialissue_filter'); + $this->filter = self::$container->get(BySocialIssueFilter::class); } public function getFilter() @@ -39,19 +41,21 @@ final class BySocialIssueFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(SocialIssue::class, 'si') ->select('si') ->getQuery() + ->setMaxResults(2) ->getResult(); $data = []; foreach ($array as $a) { $data[] = [ - 'accepted_socialissues' => $a, + 'accepted_socialissues' => new ArrayCollection([$a]), ]; } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityDateFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityDateFilterTest.php index 8d6d6b790..4cefb136a 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityDateFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityDateFilterTest.php @@ -13,12 +13,13 @@ namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Filter\ActivityDateFilter; +use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Test\Export\AbstractFilterTest; -use DateTime; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class ActivityDateFilterTest extends AbstractFilterTest @@ -29,7 +30,7 @@ final class ActivityDateFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.date_filter'); + $this->filter = self::$container->get(ActivityDateFilter::class); } public function getFilter() @@ -41,24 +42,22 @@ final class ActivityDateFilterTest extends AbstractFilterTest { return [ [ - 'date_from' => DateTime::createFromFormat('Y-m-d', '2020-01-01'), - 'date_to' => DateTime::createFromFormat('Y-m-d', '2021-01-01'), + 'date_from' => new RollingDate(RollingDate::T_FIXED_DATE, \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01')), + 'date_to' => new RollingDate(RollingDate::T_FIXED_DATE, \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01')), ], ]; } - public function getQueryBuilders(): array + public function getQueryBuilders(): iterable { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); - return [ - $em->createQueryBuilder() - ->select('count(activity.id)') - ->from(Activity::class, 'activity'), - ]; + yield $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'); + + self::ensureKernelShutdown(); } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityPresenceFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityPresenceFilterTest.php new file mode 100644 index 000000000..6fc3d10d8 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityPresenceFilterTest.php @@ -0,0 +1,76 @@ +translator = self::$container->get(TranslatorInterface::class); + $this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class); + } + + public function getFilter() + { + return new ActivityPresenceFilter($this->translatableStringHelper, $this->translator); + } + + public function getFormData() + { + self::bootKernel(); + + $presences = self::$container->get(ActivityPresenceRepositoryInterface::class) + ->findAll(); + + return [ + [ + 'presences' => $presences, + ], + [ + 'presences' => new ArrayCollection($presences), + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + yield $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'); + + self::ensureKernelShutdown(); + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php index 26d5248ae..c018efa18 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php @@ -14,10 +14,13 @@ namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Export\Filter\PersonFilters\ActivityReasonFilter; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\EntityManagerInterface; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\HttpFoundation\RequestStack; /** * @internal + * * @coversNothing */ final class ActivityReasonFilterTest extends AbstractFilterTest @@ -37,7 +40,7 @@ final class ActivityReasonFilterTest extends AbstractFilterTest $request->getLocale()->willReturn('fr'); - self::$container->get('request_stack') + self::$container->get(RequestStack::class) ->push($request->reveal()); } @@ -48,47 +51,46 @@ final class ActivityReasonFilterTest extends AbstractFilterTest public function getFormData() { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); + $data = []; - $em = self::$kernel->getContainer() - ->get('doctrine.orm.entity_manager'); + $em = self::$container + ->get(EntityManagerInterface::class); $reasons = $em->createQuery('SELECT reason ' - . 'FROM ChillActivityBundle:ActivityReason reason') + .'FROM ChillActivityBundle:ActivityReason reason') ->getResult(); // generate an array of 5 different combination of results for ($i = 0; 5 > $i; ++$i) { - $r[] = ['reasons' => new ArrayCollection(array_splice($reasons, ($i + 1) * -1))]; + $data[] = ['reasons' => new ArrayCollection(array_splice($reasons, ($i + 1) * -1))]; + $data[] = ['reasons' => array_splice($reasons, ($i + 1) * -1)]; } - return $r; + self::ensureKernelShutdown(); + + return $data; } - public function getQueryBuilders() + public function getQueryBuilders(): iterable { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); - $em = self::$kernel->getContainer() - ->get('doctrine.orm.entity_manager'); + $em = self::$container->get(EntityManagerInterface::class); - return [ - $em->createQueryBuilder() - ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity'), - $em->createQueryBuilder() - ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity') - ->join('activity.reasons', 'reasons'), - $em->createQueryBuilder() - ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity') - ->join('activity.reasons', 'reasons') - ->join('reasons.category', 'category'), - ]; + yield $em->createQueryBuilder() + ->select('count(activity.id)') + ->from('ChillActivityBundle:Activity', 'activity'); + yield $em->createQueryBuilder() + ->select('count(activity.id)') + ->from('ChillActivityBundle:Activity', 'activity') + ->join('activity.reasons', 'reasons'); + yield $em->createQueryBuilder() + ->select('count(activity.id)') + ->from('ChillActivityBundle:Activity', 'activity') + ->join('activity.reasons', 'reasons') + ->join('reasons.category', 'category'); + + self::ensureKernelShutdown(); } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityTypeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityTypeFilterTest.php index 9fd1250ca..376614228 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityTypeFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityTypeFilterTest.php @@ -15,10 +15,12 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Export\Filter\ActivityTypeFilter; use Chill\MainBundle\Test\Export\AbstractFilterTest; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class ActivityTypeFilterTest extends AbstractFilterTest @@ -37,8 +39,10 @@ final class ActivityTypeFilterTest extends AbstractFilterTest return $this->filter; } - public function getFormData(): array + public function getFormData(): iterable { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() @@ -51,25 +55,26 @@ final class ActivityTypeFilterTest extends AbstractFilterTest foreach ($array as $a) { $data[] = [ - 'types' => $a, + 'types' => new ArrayCollection([$a]), ]; + /*$data[] = [ + 'types' => [$a], + ];*/ } return $data; } - public function getQueryBuilders(): array + public function getQueryBuilders(): iterable { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); - return [ - $em->createQueryBuilder() - ->select('count(activity.id)') - ->from(Activity::class, 'activity'), - ]; + yield $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'); + + self::ensureKernelShutdown(); } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php similarity index 84% rename from src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php index 47e76e25c..d5073b625 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php @@ -9,19 +9,20 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Filter\ACPFilters\ByCreatorFilter; +use Chill\ActivityBundle\Export\Filter\ByCreatorFilter; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ -final class ByUserFilterTest extends AbstractFilterTest +final class ByCreatorFilterTest extends AbstractFilterTest { private ByCreatorFilter $filter; @@ -29,7 +30,7 @@ final class ByUserFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.byuser_filter'); + $this->filter = self::$container->get(ByCreatorFilter::class); } public function getFilter() diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/CreatorJobFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/CreatorJobFilterTest.php new file mode 100644 index 000000000..bbf9b830e --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/CreatorJobFilterTest.php @@ -0,0 +1,75 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + $this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class); + $this->translator = self::$container->get(TranslatorInterface::class); + $this->userJobRepository = self::$container->get(UserJobRepositoryInterface::class); + } + + public function getFilter() + { + return new CreatorJobFilter( + $this->translatableStringHelper, + $this->translator, + $this->userJobRepository + ); + } + + public function getFormData() + { + $this->setUp(); + $jobs = $this->userJobRepository->findAll(); + + return [ + ['jobs' => $jobs], + ]; + } + + public function getQueryBuilders() + { + self::setUp(); + + return [ + $this->entityManager->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.user', 'actuser'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserScopeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/CreatorScopeFilterTest.php similarity index 75% rename from src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserScopeFilterTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Filter/CreatorScopeFilterTest.php index 5742662b2..bf11b0320 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserScopeFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/CreatorScopeFilterTest.php @@ -9,27 +9,28 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Filter\ACPFilters\UserScopeFilter; +use Chill\ActivityBundle\Export\Filter\CreatorScopeFilter; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ -final class UserScopeFilterTest extends AbstractFilterTest +final class CreatorScopeFilterTest extends AbstractFilterTest { - private UserScopeFilter $filter; + private CreatorScopeFilter $filter; protected function setUp(): void { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.userscope_filter'); + $this->filter = self::$container->get(CreatorScopeFilter::class); } public function getFilter() @@ -39,19 +40,21 @@ final class UserScopeFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(Scope::class, 's') ->select('s') ->getQuery() + ->setMaxResults(1) ->getResult(); $data = []; foreach ($array as $a) { $data[] = [ - 'accepted_userscope' => $a, + 'scopes' => $a, ]; } @@ -60,9 +63,7 @@ final class UserScopeFilterTest extends AbstractFilterTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/EmergencyFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/EmergencyFilterTest.php similarity index 78% rename from src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/EmergencyFilterTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Filter/EmergencyFilterTest.php index 5242f337a..5ef1879a7 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/EmergencyFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/EmergencyFilterTest.php @@ -9,15 +9,16 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter; +use Chill\ActivityBundle\Export\Filter\EmergencyFilter; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class EmergencyFilterTest extends AbstractFilterTest @@ -28,7 +29,7 @@ final class EmergencyFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.emergency_filter'); + $this->filter = self::$container->get(EmergencyFilter::class); } public function getFilter() @@ -46,9 +47,7 @@ final class EmergencyFilterTest extends AbstractFilterTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/LocationTypeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/LocationTypeFilterTest.php similarity index 83% rename from src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/LocationTypeFilterTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Filter/LocationTypeFilterTest.php index c037a28b1..bf52ceee1 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/LocationTypeFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/LocationTypeFilterTest.php @@ -9,16 +9,17 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter; +use Chill\ActivityBundle\Export\Filter\LocationTypeFilter; use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class LocationTypeFilterTest extends AbstractFilterTest @@ -29,7 +30,7 @@ final class LocationTypeFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.locationtype_filter'); + $this->filter = self::$container->get(LocationTypeFilter::class); } public function getFilter() @@ -39,12 +40,14 @@ final class LocationTypeFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(LocationType::class, 'lt') ->select('lt') ->getQuery() + ->setMaxResults(1) ->getResult(); $data = []; @@ -60,9 +63,7 @@ final class LocationTypeFilterTest extends AbstractFilterTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/ActivityReasonFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/ActivityReasonFilterTest.php index b4e962ba9..6ad78579e 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/ActivityReasonFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/ActivityReasonFilterTest.php @@ -15,10 +15,12 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Export\Filter\PersonFilters\ActivityReasonFilter; use Chill\MainBundle\Test\Export\AbstractFilterTest; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class ActivityReasonFilterTest extends AbstractFilterTest @@ -39,19 +41,21 @@ final class ActivityReasonFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(ActivityReason::class, 'ar') ->select('ar') ->getQuery() + ->setMaxResults(1) ->getResult(); $data = []; foreach ($array as $a) { $data[] = [ - 'reasons' => $a, + 'reasons' => new ArrayCollection([$a]), ]; } @@ -60,9 +64,7 @@ final class ActivityReasonFilterTest extends AbstractFilterTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilterTest.php index eb296e9a4..34a9c8303 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilterTest.php @@ -15,11 +15,11 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Export\Filter\PersonFilters\PersonHavingActivityBetweenDateFilter; use Chill\MainBundle\Test\Export\AbstractFilterTest; -use DateTime; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest @@ -40,21 +40,23 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(ActivityReason::class, 'ar') ->select('ar') ->getQuery() + ->setMaxResults(1) ->getResult(); $data = []; foreach ($array as $a) { $data[] = [ - 'date_from' => DateTime::createFromFormat('Y-m-d', '2021-07-01'), - 'date_to' => DateTime::createFromFormat('Y-m-d', '2022-07-01'), - 'reasons' => $a, + 'date_from' => \DateTime::createFromFormat('Y-m-d', '2021-07-01'), + 'date_to' => \DateTime::createFromFormat('Y-m-d', '2022-07-01'), + 'reasons' => [$a], ]; } @@ -72,7 +74,8 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest return [ $em->createQueryBuilder() ->select('count(activity.id)') - ->from(Activity::class, 'activity'), + ->from(Activity::class, 'activity') + ->join('activity.person', 'person'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php index 762de529e..a9935eea4 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php @@ -12,12 +12,16 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Export\Filter\PersonFilters\PersonHavingActivityBetweenDateFilter; +use Chill\ActivityBundle\Repository\ActivityReasonRepository; use Chill\MainBundle\Test\Export\AbstractFilterTest; -use DateTime; -use function array_slice; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; /** * @internal + * * @coversNothing */ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest @@ -31,11 +35,11 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest $this->filter = self::$container->get('chill.activity.export.person_having_an_activity_between_date_filter'); $request = $this->prophesize() - ->willExtend(\Symfony\Component\HttpFoundation\Request::class); + ->willExtend(Request::class); $request->getLocale()->willReturn('fr'); - self::$container->get('request_stack') + self::$container->get(RequestStack::class) ->push($request->reveal()); } @@ -46,8 +50,8 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest public function getFormData() { - $date_from = DateTime::createFromFormat('Y-m-d', '2015-01-15'); - $date_to = new DateTime(); // today + $date_from = \DateTime::createFromFormat('Y-m-d', '2015-01-15'); + $date_to = new \DateTime(); // today $reasons = $this->getActivityReasons(); $data = []; @@ -56,7 +60,7 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest $data[] = [ 'date_from' => $date_from, 'date_to' => $date_to, - 'reasons' => array_slice($reasons, 0, 1 + $i), + 'reasons' => \array_slice($reasons, 0, 1 + $i), ]; } @@ -65,26 +69,24 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest public function getQueryBuilders() { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); - $em = self::$kernel->getContainer() - ->get('doctrine.orm.entity_manager'); + $em = self::$container + ->get(EntityManagerInterface::class); - return [ - $em->createQueryBuilder() - ->select('count(person.id)') - ->from('ChillPersonBundle:Person', 'person') - // add a fake where clause - ->where('person.id > 0'), - $em->createQueryBuilder() - ->select('count(person.id)') - ->from('ChillActivityBundle:Activity', 'activity') - ->join('activity.person', 'person') - // add a fake where clause - ->where('person.id > 0'), - ]; + yield $em->createQueryBuilder() + ->select('count(person.id)') + ->from('ChillPersonBundle:Person', 'person') + // add a fake where clause + ->where('person.id > 0'); + yield $em->createQueryBuilder() + ->select('count(person.id)') + ->from('ChillActivityBundle:Activity', 'activity') + ->join('activity.person', 'person') + // add a fake where clause + ->where('person.id > 0'); + + self::ensureKernelShutdown(); } /** @@ -94,12 +96,14 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest */ private function getActivityReasons() { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); - return self::$kernel->getContainer() - ->get('chill_activity.repository.reason') - ->findAll(); + $managerRegistry = self::$container->get(ManagerRegistry::class); + $requestStack = new RequestStack(); + $requestStack->push(new Request()); + + $repository = new ActivityReasonRepository($managerRegistry, $requestStack); + + return $repository->findAll(); } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonsFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonsFilterTest.php new file mode 100644 index 000000000..cefc226e7 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonsFilterTest.php @@ -0,0 +1,72 @@ +personRender = self::$container->get(PersonRenderInterface::class); + } + + public function getFilter() + { + return new PersonsFilter($this->personRender); + } + + public function getFormData() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $persons = $em->createQuery('SELECT p FROM '.Person::class.' p ') + ->setMaxResults(2) + ->getResult(); + + self::ensureKernelShutdown(); + + return [ + [ + 'accepted_persons' => $persons, + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + yield $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'); + + self::ensureKernelShutdown(); + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/SentReceivedFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/SentReceivedFilterTest.php similarity index 79% rename from src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/SentReceivedFilterTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Filter/SentReceivedFilterTest.php index 6b16daa99..f368e69fa 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/SentReceivedFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/SentReceivedFilterTest.php @@ -9,15 +9,16 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Filter\ACPFilters\SentReceivedFilter; +use Chill\ActivityBundle\Export\Filter\SentReceivedFilter; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class SentReceivedFilterTest extends AbstractFilterTest @@ -28,7 +29,7 @@ final class SentReceivedFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.sentreceived_filter'); + $this->filter = self::$container->get(SentReceivedFilter::class); } public function getFilter() @@ -46,9 +47,7 @@ final class SentReceivedFilterTest extends AbstractFilterTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php similarity index 83% rename from src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserFilterTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php index fda613d2e..ef8f4c6b2 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php @@ -9,16 +9,17 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; +namespace Chill\ActivityBundle\Tests\Export\Filter; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Filter\ACPFilters\UserFilter; +use Chill\ActivityBundle\Export\Filter\UserFilter; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class UserFilterTest extends AbstractFilterTest @@ -29,7 +30,7 @@ final class UserFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.user_filter'); + $this->filter = self::$container->get(UserFilter::class); } public function getFilter() @@ -39,12 +40,14 @@ final class UserFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(User::class, 'u') ->select('u') ->getQuery() + ->setMaxResults(1) ->getResult(); $data = []; @@ -60,9 +63,7 @@ final class UserFilterTest extends AbstractFilterTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php b/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php index 3ed6b4409..3d1b31f08 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php @@ -13,14 +13,13 @@ namespace Chill\ActivityBundle\Tests\Form; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Form\ActivityType; -use DateTime; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; -use Symfony\Component\Security\Core\Role\Role; /** * @internal + * * @coversNothing */ final class ActivityTypeTest extends KernelTestCase @@ -30,11 +29,6 @@ final class ActivityTypeTest extends KernelTestCase */ protected $center; - /** - * @var \Symfony\Component\DependencyInjection\ContainerInterface - */ - protected $container; - /** * @var \Symfony\Component\Form\FormBuilderInterface */ @@ -85,7 +79,7 @@ final class ActivityTypeTest extends KernelTestCase $form = $this->formBuilder ->add('activity', ActivityType::class, [ 'center' => $this->center, - 'role' => new Role('CHILL_ACTIVITY_CREATE'), + 'role' => 'CHILL_ACTIVITY_CREATE', ]) ->getForm(); @@ -101,7 +95,7 @@ final class ActivityTypeTest extends KernelTestCase $form = $this->formBuilder ->add('activity', ActivityType::class, [ 'center' => $this->center, - 'role' => new Role('CHILL_ACTIVITY_CREATE'), + 'role' => 'CHILL_ACTIVITY_CREATE', ]) ->getForm(); @@ -145,7 +139,7 @@ final class ActivityTypeTest extends KernelTestCase public function testFormWithActivityHavingDifferentTime() { $activity = new Activity(); - $activity->setDurationTime(DateTime::createFromFormat('U', 60)); + $activity->setDurationTime(\DateTime::createFromFormat('U', 60)); $builder = $this->container ->get('form.factory') @@ -157,7 +151,7 @@ final class ActivityTypeTest extends KernelTestCase $form = $builder ->add('activity', ActivityType::class, [ 'center' => $this->center, - 'role' => new Role('CHILL_ACTIVITY_CREATE'), + 'role' => 'CHILL_ACTIVITY_CREATE', ]) ->getForm(); diff --git a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php index a8d85daa6..014c3ae9f 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Form\Test\TypeTestCase; * Test translatableActivityReason. * * @internal + * * @coversNothing */ final class TranslatableActivityReasonTest extends TypeTestCase @@ -34,7 +35,10 @@ final class TranslatableActivityReasonTest extends TypeTestCase parent::setUp(); } - public function testSimple() + /** + * @doesNotPerformAssertions + */ + public function testSimple(): never { $translatableActivityReasonType = new PickActivityReasonType( $this->getTranslatableStringHelper() diff --git a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityTypeTest.php b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityTypeTest.php index 770b88cf4..f3163f664 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityTypeTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityTypeTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; /** * @internal + * * @coversNothing */ final class TranslatableActivityTypeTest extends KernelTestCase @@ -27,18 +28,11 @@ final class TranslatableActivityTypeTest extends KernelTestCase */ protected $builder; - /** - * @var \Symfony\Component\DependencyInjection\ContainerInterface - */ - protected $container; - protected function setUp(): void { self::bootKernel(); - $this->container = self::$kernel->getContainer(); - - $this->builder = $this->container + $this->builder = self::$container ->get('form.factory') ->createBuilder(FormType::class, null, [ 'csrf_protection' => false, @@ -64,7 +58,7 @@ final class TranslatableActivityTypeTest extends KernelTestCase $this->assertTrue($form->isSynchronized()); $this->assertInstanceOf( - \Chill\ActivityBundle\Entity\ActivityType::class, + ActivityType::class, $form->getData()['type'], 'The data is an instance of Chill\\ActivityBundle\\Entity\\ActivityType' ); @@ -89,11 +83,9 @@ final class TranslatableActivityTypeTest extends KernelTestCase } /** - * @param mixed $active - * - * @return \Chill\ActivityBundle\Entity\ActivityType + * @return ActivityType */ - protected function getRandomType($active = true) + protected function getRandomType(mixed $active = true) { $types = $this->container->get('doctrine.orm.entity_manager') ->getRepository(ActivityType::class) diff --git a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php new file mode 100644 index 000000000..67b3c1f5d --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php @@ -0,0 +1,352 @@ +authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class); + $this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class); + $this->activityRepository = self::$container->get(ActivityRepository::class); + $this->entityManager = self::$container->get(EntityManagerInterface::class); + $this->security = self::$container->get(Security::class); + + $this->requestStack = $requestStack = new RequestStack(); + $request = $this->prophesize(Request::class); + $request->getLocale()->willReturn('fr'); + $request->getDefaultLocale()->willReturn('fr'); + $requestStack->push($request->reveal()); + } + + /** + * @dataProvider provideDataFindByAccompanyingPeriod + */ + public function testFindByAccompanyingPeriod(AccompanyingPeriod $period, User $user, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): void + { + $security = $this->prophesize(Security::class); + $security->isGranted($role, $period)->willReturn(true); + $security->getUser()->willReturn($user); + + $repository = new ActivityACLAwareRepository( + $this->authorizationHelperForCurrentUser, + $this->centerResolverManager, + $this->activityRepository, + $this->entityManager, + $security->reveal(), + $this->requestStack + ); + + $actual = $repository->findByAccompanyingPeriod($period, $role, $start, $limit, $orderBy, $filters); + + self::assertIsArray($actual); + } + + /** + * @dataProvider provideDataFindByAccompanyingPeriod + */ + public function testfindByAccompanyingPeriodSimplified(AccompanyingPeriod $period, User $user, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): void + { + $security = $this->prophesize(Security::class); + $security->isGranted($role, $period)->willReturn(true); + $security->getUser()->willReturn($user); + + $repository = new ActivityACLAwareRepository( + $this->authorizationHelperForCurrentUser, + $this->centerResolverManager, + $this->activityRepository, + $this->entityManager, + $security->reveal(), + $this->requestStack + ); + + $actual = $repository->findByAccompanyingPeriodSimplified($period); + + self::assertIsArray($actual); + } + + /** + * @dataProvider provideDataFindByAccompanyingPeriod + */ + public function testFindActivityTypeByAccompanyingPeriod(AccompanyingPeriod $period, User $user, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): void + { + $security = $this->prophesize(Security::class); + $security->isGranted($role, $period)->willReturn(true); + $security->getUser()->willReturn($user); + + $repository = new ActivityACLAwareRepository( + $this->authorizationHelperForCurrentUser, + $this->centerResolverManager, + $this->activityRepository, + $this->entityManager, + $security->reveal(), + $this->requestStack + ); + + $actual = $repository->findActivityTypeByAssociated($period); + + self::assertIsArray($actual); + } + + /** + * @dataProvider provideDataFindByPerson + */ + public function testFindActivityTypeByPerson(Person $person, User $user, array $centers, array $scopes, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): void + { + $role = ActivityVoter::SEE; + $centerResolver = $this->prophesize(CenterResolverManagerInterface::class); + $centerResolver->resolveCenters($person)->willReturn($centers); + + $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $authorizationHelper->getReachableScopes($role, Argument::type(Center::class)) + ->willReturn($scopes); + + $security = $this->prophesize(Security::class); + $security->isGranted($role, Argument::type(AccompanyingPeriod::class))->willReturn(true); + $security->getUser()->willReturn($user); + + $repository = new ActivityACLAwareRepository( + $authorizationHelper->reveal(), + $centerResolver->reveal(), + $this->activityRepository, + $this->entityManager, + $security->reveal(), + $this->requestStack + ); + + $actual = $repository->findByPerson($person, $role, $start, $limit, $orderBy, $filters); + + self::assertIsArray($actual); + } + + /** + * @dataProvider provideDataFindByPerson + */ + public function testFindByPerson(Person $person, User $user, array $centers, array $scopes, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): void + { + $centerResolver = $this->prophesize(CenterResolverManagerInterface::class); + $centerResolver->resolveCenters($person)->willReturn($centers); + + $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $authorizationHelper->getReachableScopes($role, Argument::type(Center::class)) + ->willReturn($scopes); + + $security = $this->prophesize(Security::class); + $security->isGranted($role, Argument::type(AccompanyingPeriod::class))->willReturn(true); + $security->getUser()->willReturn($user); + + $repository = new ActivityACLAwareRepository( + $authorizationHelper->reveal(), + $centerResolver->reveal(), + $this->activityRepository, + $this->entityManager, + $security->reveal(), + $this->requestStack + ); + + $actual = $repository->findByPerson($person, $role, $start, $limit, $orderBy, $filters); + + self::assertIsArray($actual); + } + + public function provideDataFindByPerson(): iterable + { + $this->setUp(); + + /** @var Person $person */ + if (null === $person = $this->entityManager->createQueryBuilder() + ->select('p')->from(Person::class, 'p')->setMaxResults(1) + ->getQuery()->getSingleResult()) { + throw new \RuntimeException('person not found'); + } + + /** @var AccompanyingPeriod $period1 */ + if (null === $period1 = $this->entityManager + ->createQueryBuilder() + ->select('a') + ->from(AccompanyingPeriod::class, 'a') + ->setMaxResults(1) + ->getQuery() + ->getSingleResult()) { + throw new \RuntimeException('no period found'); + } + + /** @var AccompanyingPeriod $period2 */ + if (null === $period2 = $this->entityManager + ->createQueryBuilder() + ->select('a') + ->from(AccompanyingPeriod::class, 'a') + ->where('a.id > :pid') + ->setParameter('pid', $period1->getId()) + ->setMaxResults(1) + ->getQuery() + ->getSingleResult()) { + throw new \RuntimeException('no second period found'); + } + // add a period + $period1->addPerson($person); + $period2->addPerson($person); + $period1->getParticipationsContainsPerson($person)->first()->setEndDate( + (new \DateTime('now'))->add(new \DateInterval('P1M')) + ); + + 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() + ) { + throw new \RuntimeException('no jobs found'); + } + + if (null === $user = $this->entityManager + ->createQueryBuilder() + ->select('u') + ->from(User::class, 'u') + ->setMaxResults(1) + ->getQuery() + ->getSingleResult() + ) { + throw new \RuntimeException('no user found'); + } + + if ([] === $centers = $this->entityManager->createQueryBuilder() + ->select('c')->from(Center::class, 'c')->setMaxResults(2)->getQuery() + ->getResult()) { + throw new \RuntimeException('no centers found'); + } + + if ([] === $scopes = $this->entityManager->createQueryBuilder() + ->select('s')->from(Scope::class, 's')->setMaxResults(2)->getQuery() + ->getResult()) { + throw new \RuntimeException('no scopes found'); + } + + yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], []]; + yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['my_activities' => true]]; + yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['types' => $types]]; + yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['jobs' => $jobs]]; + yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]]; + 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')]]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php index ce4f318e3..c32714641 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php @@ -20,7 +20,6 @@ use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\EntityManagerInterface; -use phpseclib3\Math\BinaryField; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -28,6 +27,7 @@ use Symfony\Component\Security\Core\Security; /** * @internal + * * @coversNothing */ class ActivityDocumentACLAwareRepositoryTest extends KernelTestCase @@ -104,23 +104,22 @@ class ActivityDocumentACLAwareRepositoryTest extends KernelTestCase { $this->setUp(); - if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p WHERE SIZE(p.accompanyingPeriodParticipations) > 0 ") + if (null === $person = $this->entityManager->createQuery('SELECT p FROM '.Person::class.' p WHERE SIZE(p.accompanyingPeriodParticipations) > 0 ') ->setMaxResults(1) ->getSingleResult()) { - throw new \RuntimeException("no person in dtabase"); + throw new \RuntimeException('no person in dtabase'); } - if ([] === $scopes = $this->entityManager->createQuery("SELECT s FROM " . Scope::class . " s ")->setMaxResults(5)->getResult()) { - throw new \RuntimeException("no scopes in database"); + if ([] === $scopes = $this->entityManager->createQuery('SELECT s FROM '.Scope::class.' s ')->setMaxResults(5)->getResult()) { + throw new \RuntimeException('no scopes in database'); } yield [$person, [], true, null, null, null]; yield [$person, $scopes, true, null, null, null]; - yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), null, null]; - yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), null]; - yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"]; - yield [$person, $scopes, true, null, new \DateTimeImmutable("1 week ago"), "content"]; - yield [$person, [], true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"]; + yield [$person, $scopes, true, new \DateTimeImmutable('1 month ago'), null, null]; + yield [$person, $scopes, true, new \DateTimeImmutable('1 month ago'), new \DateTimeImmutable('1 week ago'), null]; + yield [$person, $scopes, true, new \DateTimeImmutable('1 month ago'), new \DateTimeImmutable('1 week ago'), 'content']; + yield [$person, $scopes, true, null, new \DateTimeImmutable('1 week ago'), 'content']; + yield [$person, [], true, new \DateTimeImmutable('1 month ago'), new \DateTimeImmutable('1 week ago'), 'content']; } - } diff --git a/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php b/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php index 1fb90a4ae..e3ab7b60a 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php @@ -24,6 +24,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** * @internal + * * @coversNothing */ final class ActivityVoterTest extends KernelTestCase @@ -127,7 +128,7 @@ final class ActivityVoterTest extends KernelTestCase /** * @dataProvider dataProvider_testVoteAction * - * @param type $expectedResult + * @param type $expectedResult * @param string $attribute * @param string $message */ @@ -160,7 +161,7 @@ final class ActivityVoterTest extends KernelTestCase { $token = $this->prophet->prophesize(); $token - ->willImplement('\\' . \Symfony\Component\Security\Core\Authentication\Token\TokenInterface::class); + ->willImplement('\\'.\Symfony\Component\Security\Core\Authentication\Token\TokenInterface::class); if (null === $user) { $token->getUser()->willReturn(null); diff --git a/src/Bundle/ChillActivityBundle/Tests/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContextTest.php b/src/Bundle/ChillActivityBundle/Tests/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContextTest.php new file mode 100644 index 000000000..ff9f30c30 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContextTest.php @@ -0,0 +1,139 @@ +listActivitiesByAccompanyingPeriodContext = self::$container->get(ListActivitiesByAccompanyingPeriodContext::class); + $this->accompanyingPeriodRepository = self::$container->get(AccompanyingPeriodRepository::class); + $this->userRepository = self::$container->get(UserRepositoryInterface::class); + } + + /** + * @dataProvider provideAccompanyingPeriod + */ + public function testGetDataWithoutFilteringActivityNorWorks(int $accompanyingPeriodId, int $userId): void + { + $context = $this->getContext(); + $template = new DocGeneratorTemplate(); + $template->setOptions([ + 'mainPerson' => false, + 'person1' => false, + 'person2' => false, + 'thirdParty' => false, + ]); + + $data = $context->getData( + $template, + $this->accompanyingPeriodRepository->find($accompanyingPeriodId), + ['myActivitiesOnly' => false, 'myWorksOnly' => false] + ); + + self::assertIsArray($data); + self::assertArrayHasKey('activities', $data); + self::assertIsArray($data['activities']); + self::assertGreaterThan(0, count($data['activities'])); + self::assertIsArray($data['activities'][0]); + self::assertArrayHasKey('user', $data['activities'][0]); + self::assertIsArray($data['activities'][0]['user']); + } + + /** + * @dataProvider provideAccompanyingPeriod + */ + public function testGetDataWithoutFilteringActivityByUser(int $accompanyingPeriodId, int $userId): void + { + $context = $this->getContext(); + $template = new DocGeneratorTemplate(); + $template->setOptions([ + 'mainPerson' => false, + 'person1' => false, + 'person2' => false, + 'thirdParty' => false, + ]); + + $data = $context->getData( + $template, + $this->accompanyingPeriodRepository->find($accompanyingPeriodId), + ['myActivitiesOnly' => true, 'myWorksOnly' => false, 'creator' => $this->userRepository->find($userId)] + ); + + self::assertIsArray($data); + self::assertArrayHasKey('activities', $data); + self::assertIsArray($data['activities']); + self::assertGreaterThan(0, count($data['activities'])); + self::assertIsArray($data['activities'][0]); + self::assertArrayHasKey('user', $data['activities'][0]); + self::assertIsArray($data['activities'][0]['user']); + } + + public static function provideAccompanyingPeriod(): array + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + if (null === $period = $em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a') + ->setMaxResults(1) + ->getSingleResult()) { + throw new \RuntimeException('no period found'); + } + + if (null === $user = $em->createQuery('SELECT u FROM '.User::class.' u') + ->setMaxResults(1) + ->getSingleResult() + ) { + throw new \RuntimeException('no user found'); + } + + $activity = new Activity(); + $activity + ->setAccompanyingPeriod($period) + ->setUser($user) + ->setDate(new \DateTime()); + + $em->persist($activity); + $em->flush(); + + self::ensureKernelShutdown(); + + return [ + [$period->getId(), $user->getId()], + ]; + } + + private function getContext(): ListActivitiesByAccompanyingPeriodContext + { + return $this->listActivitiesByAccompanyingPeriodContext; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Timeline/TimelineProviderTest.php b/src/Bundle/ChillActivityBundle/Tests/Timeline/TimelineProviderTest.php index 664196ad4..b7796c9ba 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Timeline/TimelineProviderTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Timeline/TimelineProviderTest.php @@ -15,11 +15,15 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** * @internal + * * @coversNothing */ final class TimelineProviderTest extends WebTestCase { - public function testAnActivityIsShownOnTimeline() + /** + * @doesNotPerformAssertions + */ + public function testAnActivityIsShownOnTimeline(): never { $this->markTestSkipped('we have to write fixtures before writing this tests'); } diff --git a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php index 2c9272b08..23a75e9fe 100644 --- a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php +++ b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php @@ -19,40 +19,23 @@ use Chill\MainBundle\Timeline\TimelineProviderInterface; use Chill\MainBundle\Timeline\TimelineSingleQuery; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\EntityManagerInterface; -use LogicException; -use RuntimeException; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\User\UserInterface; -use function implode; -use function in_array; -use function strtr; - class TimelineActivityProvider implements TimelineProviderInterface { private const SUPPORTED_CONTEXTS = ['center', 'person']; - protected ActivityACLAwareRepository $aclAwareRepository; - - protected EntityManagerInterface $em; - - protected AuthorizationHelperInterface $helper; - protected UserInterface $user; public function __construct( - EntityManagerInterface $em, - AuthorizationHelperInterface $helper, + protected EntityManagerInterface $em, + protected AuthorizationHelperInterface $helper, TokenStorageInterface $storage, - ActivityACLAwareRepository $aclAwareRepository + protected ActivityACLAwareRepository $aclAwareRepository ) { - $this->em = $em; - $this->helper = $helper; - $this->aclAwareRepository = $aclAwareRepository; - if (!$storage->getToken()->getUser() instanceof User) { - throw new RuntimeException('A user should be authenticated !'); + throw new \RuntimeException('A user should be authenticated !'); } $this->user = $storage->getToken()->getUser(); @@ -71,10 +54,10 @@ class TimelineActivityProvider implements TimelineProviderInterface return TimelineSingleQuery::fromArray([ 'id' => $metadataActivity->getTableName() - . '.' . $metadataActivity->getColumnName('id'), + .'.'.$metadataActivity->getColumnName('id'), 'type' => 'activity', 'date' => $metadataActivity->getTableName() - . '.' . $metadataActivity->getColumnName('date'), + .'.'.$metadataActivity->getColumnName('date'), 'FROM' => $this->getFromClausePerson(), 'WHERE' => $where, 'parameters' => $parameters, @@ -100,7 +83,7 @@ class TimelineActivityProvider implements TimelineProviderInterface $this->checkContext($context); return [ - 'template' => 'ChillActivityBundle:Timeline:activity_person_context.html.twig', + 'template' => '@ChillActivity/Timeline/activity_person_context.html.twig', 'template_data' => [ 'activity' => $entity, 'context' => $context, @@ -116,17 +99,12 @@ class TimelineActivityProvider implements TimelineProviderInterface /** * Check if the context is supported. * - * @throws LogicException if the context is not supported + * @throws \LogicException if the context is not supported */ private function checkContext(string $context) { - if (false === in_array($context, self::SUPPORTED_CONTEXTS, true)) { - throw new LogicException( - sprintf( - "The context '%s' is not supported. Currently only 'person' is supported", - $context - ) - ); + if (false === \in_array($context, self::SUPPORTED_CONTEXTS, true)) { + throw new \LogicException(sprintf("The context '%s' is not supported. Currently only 'person' is supported", $context)); } } @@ -151,8 +129,8 @@ class TimelineActivityProvider implements TimelineProviderInterface $parameters = []; $metadataActivity = $this->em->getClassMetadata(Activity::class); $associationMapping = $metadataActivity->getAssociationMapping('person'); - $role = new Role('CHILL_ACTIVITY_SEE'); - $reachableScopes = $this->helper->getReachableScopes($this->user, $role->getRole(), $person->getCenter()); + $role = 'CHILL_ACTIVITY_SEE'; + $reachableScopes = $this->helper->getReachableScopes($this->user, $role, $person->getCenter()); $whereClause = ' {activity.person_id} = ? AND {activity.scope_id} IN ({scopes_ids}) '; $scopes_ids = []; @@ -161,8 +139,8 @@ class TimelineActivityProvider implements TimelineProviderInterface // loop on reachable scopes foreach ($reachableScopes as $scope) { - /** @phpstan-ignore-next-line */ - if (in_array($scope->getId(), $scopes_ids, true)) { + /* @phpstan-ignore-next-line */ + if (\in_array($scope->getId(), $scopes_ids, true)) { continue; } $scopes_ids[] = '?'; @@ -170,13 +148,13 @@ class TimelineActivityProvider implements TimelineProviderInterface } return [ - strtr( + \strtr( $whereClause, [ '{activity.person_id}' => $associationMapping['joinColumns'][0]['name'], - '{activity.scope_id}' => $metadataActivity->getTableName() . '.' . + '{activity.scope_id}' => $metadataActivity->getTableName().'.'. $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'], - '{scopes_ids}' => implode(', ', $scopes_ids), + '{scopes_ids}' => \implode(', ', $scopes_ids), ] ), $parameters, diff --git a/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php b/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php index 393b33322..aafe5f9b5 100644 --- a/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php +++ b/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php @@ -18,9 +18,9 @@ use Symfony\Component\Validator\Constraint; */ class ActivityValidity extends Constraint { - public const IS_REQUIRED_MESSAGE = ' is required'; + final public const IS_REQUIRED_MESSAGE = ' is required'; - public const ROOT_MESSAGE = 'For this type of activity, '; + final public const ROOT_MESSAGE = 'For this type of activity, '; public $noPersonsMessage = 'For this type of activity, you must add at least one person'; @@ -39,6 +39,6 @@ class ActivityValidity extends Constraint public function makeIsRequiredMessage(string $property) { - return self::ROOT_MESSAGE . $property . self::IS_REQUIRED_MESSAGE; + return self::ROOT_MESSAGE.$property.self::IS_REQUIRED_MESSAGE; } } diff --git a/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php b/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php index 11b4b61e0..21ee6185e 100644 --- a/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php +++ b/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php @@ -18,9 +18,6 @@ use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; -use function array_merge; -use function count; - class ActivityValidityValidator extends ConstraintValidator { public function validate($activity, Constraint $constraint) @@ -33,106 +30,106 @@ class ActivityValidityValidator extends ConstraintValidator throw new UnexpectedValueException($activity, Activity::class); } - if ($activity->getActivityType()->getPersonsVisible() === 2 && count($activity->getPersons()) === 0) { + if (2 === $activity->getActivityType()->getPersonsVisible() && 0 === \count($activity->getPersons())) { $this->context ->buildViolation($constraint->noPersonsMessage) ->addViolation(); } - if ($activity->getActivityType()->getUsersVisible() === 2 && count($activity->getUsers()) === 0) { + if (2 === $activity->getActivityType()->getUsersVisible() && 0 === \count($activity->getUsers())) { $this->context ->buildViolation($constraint->noUsersMessage) ->addViolation(); } - if ($activity->getActivityType()->getThirdPartiesVisible() === 2 && count($activity->getThirdParties()) === 0) { + if (2 === $activity->getActivityType()->getThirdPartiesVisible() && 0 === \count($activity->getThirdParties())) { $this->context ->buildViolation($constraint->noThirdPartiesMessage) ->addViolation(); } - if ($activity->getActivityType()->getUserVisible() === 2 && null === $activity->getUser()) { + if (2 === $activity->getActivityType()->getUserVisible() && null === $activity->getUser()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('user')) ->addViolation(); } - if ($activity->getActivityType()->getDateVisible() === 2 && null === $activity->getDate()) { + if (2 === $activity->getActivityType()->getDateVisible() && null === $activity->getDate()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('date')) ->addViolation(); } - if ($activity->getActivityType()->getLocationVisible() === 2 && null === $activity->getLocation()) { + if (2 === $activity->getActivityType()->getLocationVisible() && null === $activity->getLocation()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('location')) ->addViolation(); } - if ($activity->getActivityType()->getDurationTimeVisible() === 2 && null === $activity->getDurationTime()) { + if (2 === $activity->getActivityType()->getDurationTimeVisible() && null === $activity->getDurationTime()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('duration time')) ->addViolation(); } - if ($activity->getActivityType()->getTravelTimeVisible() === 2 && null === $activity->getTravelTime()) { + if (2 === $activity->getActivityType()->getTravelTimeVisible() && null === $activity->getTravelTime()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('travel time')) ->addViolation(); } - if ($activity->getActivityType()->getAttendeeVisible() === 2 && null === $activity->getAttendee()) { + if (2 === $activity->getActivityType()->getAttendeeVisible() && null === $activity->getAttendee()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('attendee')) ->addViolation(); } - if ($activity->getActivityType()->getReasonsVisible() === 2 && null === $activity->getReasons()) { + if (2 === $activity->getActivityType()->getReasonsVisible() && null === $activity->getReasons()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('reasons')) ->addViolation(); } - if ($activity->getActivityType()->getCommentVisible() === 2 && null === $activity->getComment()) { + if (2 === $activity->getActivityType()->getCommentVisible() && null === $activity->getComment()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('comment')) ->addViolation(); } - if ($activity->getActivityType()->getSentReceivedVisible() === 2 && null === $activity->getSentReceived()) { + if (2 === $activity->getActivityType()->getSentReceivedVisible() && null === $activity->getSentReceived()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('sent/received')) ->addViolation(); } - if ($activity->getActivityType()->getDocumentsVisible() === 2 && null === $activity->getDocuments()) { + if (2 === $activity->getActivityType()->getDocumentsVisible() && null === $activity->getDocuments()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('document')) ->addViolation(); } - if ($activity->getActivityType()->getEmergencyVisible() === 2 && null === $activity->getEmergency()) { + if (2 === $activity->getActivityType()->getEmergencyVisible() && null === $activity->getEmergency()) { $this->context ->buildViolation($constraint->makeIsRequiredMessage('emergency')) ->addViolation(); } - if ($activity->getActivityType()->getSocialIssuesVisible() === 2 && $activity->getSocialIssues()->count() === 0) { + if (2 === $activity->getActivityType()->getSocialIssuesVisible() && 0 === $activity->getSocialIssues()->count()) { $this->context ->buildViolation($constraint->socialIssuesMessage) ->addViolation(); } - if ($activity->getActivityType()->getSocialActionsVisible() === 2 && $activity->getSocialActions()->count() === 0) { + if (2 === $activity->getActivityType()->getSocialActionsVisible() && 0 === $activity->getSocialActions()->count()) { // check if a social action may be added $actions = []; foreach ($activity->getSocialIssues() as $socialIssue) { /** @var SocialIssue $socialIssue */ - $actions = array_merge($actions, $socialIssue->getRecursiveSocialActions()->toArray()); + $actions = \array_merge($actions, $socialIssue->getRecursiveSocialActions()->toArray()); } - if (0 < count($actions)) { + if (0 < \count($actions)) { $this->context ->buildViolation($constraint->socialActionsMessage) ->addViolation(); diff --git a/src/Bundle/ChillActivityBundle/config/routes.yaml b/src/Bundle/ChillActivityBundle/config/routes.yaml index d20f47013..e5539c753 100644 --- a/src/Bundle/ChillActivityBundle/config/routes.yaml +++ b/src/Bundle/ChillActivityBundle/config/routes.yaml @@ -1,22 +1,6 @@ -chill_activity_activity: - resource: "@ChillActivityBundle/config/routes/activity.yaml" - prefix: / - -chill_activity_activityreason: - resource: "@ChillActivityBundle/config/routes/activityreason.yaml" - prefix: / - -chill_activity_activityreasoncategory: - resource: "@ChillActivityBundle/config/routes/activityreasoncategory.yaml" - prefix: / - -chill_activity_admin_index: - path: /{_locale}/admin/activity - controller: Chill\ActivityBundle\Controller\AdminController::indexActivityAction - -chill_admin_activity_redirect_to_admin_index: - path: /{_locale}/admin/activity_redirect_to_main - controller: Chill\ActivityBundle\Controller\AdminController::redirectToAdminIndexAction +chill_activity_routes: + resource: '@ChillActivityBundle/Controller' + type: annotation chill_activity_type_admin: path: /{_locale}/admin/activity/type diff --git a/src/Bundle/ChillActivityBundle/config/routes/activity.yaml b/src/Bundle/ChillActivityBundle/config/routes/activity.yaml deleted file mode 100644 index 179de905b..000000000 --- a/src/Bundle/ChillActivityBundle/config/routes/activity.yaml +++ /dev/null @@ -1,26 +0,0 @@ -chill_activity_activity_list: - path: /{_locale}/activity/ - controller: Chill\ActivityBundle\Controller\ActivityController::listAction - -chill_activity_activity_show: - path: /{_locale}/activity/{id}/show - controller: Chill\ActivityBundle\Controller\ActivityController::showAction - -chill_activity_activity_select_type: - path: /{_locale}/activity/select-type - controller: Chill\ActivityBundle\Controller\ActivityController::selectTypeAction - -chill_activity_activity_new: - path: /{_locale}/activity/new - controller: Chill\ActivityBundle\Controller\ActivityController::newAction - methods: [POST, GET] - -chill_activity_activity_edit: - path: /{_locale}/activity/{id}/edit - controller: Chill\ActivityBundle\Controller\ActivityController::editAction - methods: [GET, POST, PUT] - -chill_activity_activity_delete: - path: /{_locale}/activity/{id}/delete - controller: Chill\ActivityBundle\Controller\ActivityController::deleteAction - methods: [GET, POST, DELETE] diff --git a/src/Bundle/ChillActivityBundle/config/routes/activityreason.yaml b/src/Bundle/ChillActivityBundle/config/routes/activityreason.yaml deleted file mode 100644 index 59473d9f0..000000000 --- a/src/Bundle/ChillActivityBundle/config/routes/activityreason.yaml +++ /dev/null @@ -1,30 +0,0 @@ -chill_activity_activityreason: - path: /{_locale}/admin/activityreason/ - controller: Chill\ActivityBundle\Controller\ActivityReasonController::indexAction - -chill_activity_activityreason_show: - path: /{_locale}/admin/activityreason/{id}/show - controller: Chill\ActivityBundle\Controller\ActivityReasonController::showAction - -chill_activity_activityreason_new: - path: /{_locale}/admin/activityreason/new - controller: Chill\ActivityBundle\Controller\ActivityReasonController::newAction - -chill_activity_activityreason_create: - path: /{_locale}/admin/activityreason/create - controller: Chill\ActivityBundle\Controller\ActivityReasonController::createAction - methods: POST - -chill_activity_activityreason_edit: - path: /{_locale}/admin/activityreason/{id}/edit - controller: Chill\ActivityBundle\Controller\ActivityReasonController::editAction - -chill_activity_activityreason_update: - path: /{_locale}/admin/activityreason/{id}/update - controller: Chill\ActivityBundle\Controller\ActivityReasonController::updateAction - methods: [POST, PUT] - -chill_activity_activityreason_delete: - path: /{_locale}/admin/activityreason/{id}/delete - controller: Chill\ActivityBundle\Controller\ActivityReasonController::deleteAction - methods: [POST, DELETE] diff --git a/src/Bundle/ChillActivityBundle/config/routes/activityreasoncategory.yaml b/src/Bundle/ChillActivityBundle/config/routes/activityreasoncategory.yaml deleted file mode 100644 index 1487a1e24..000000000 --- a/src/Bundle/ChillActivityBundle/config/routes/activityreasoncategory.yaml +++ /dev/null @@ -1,30 +0,0 @@ -chill_activity_activityreasoncategory: - path: /{_locale}/admin/activityreasoncategory/ - controller: Chill\ActivityBundle\Controller\ActivityReasonCategoryController::indexAction - -chill_activity_activityreasoncategory_show: - path: /{_locale}/admin/activityreasoncategory/{id}/show - controller: Chill\ActivityBundle\Controller\ActivityReasonCategoryController::showAction - -chill_activity_activityreasoncategory_new: - path: /{_locale}/admin/activityreasoncategory/new - controller: Chill\ActivityBundle\Controller\ActivityReasonCategoryController::newAction - -chill_activity_activityreasoncategory_create: - path: /{_locale}/admin/activityreasoncategory/create - controller: Chill\ActivityBundle\Controller\ActivityReasonCategoryController::createAction - methods: POST - -chill_activity_activityreasoncategory_edit: - path: /{_locale}/admin/activityreasoncategory/{id}/edit - controller: Chill\ActivityBundle\Controller\ActivityReasonCategoryController::editAction - -chill_activity_activityreasoncategory_update: - path: /{_locale}/admin/activityreasoncategory/{id}/update - controller: Chill\ActivityBundle\Controller\ActivityReasonCategoryController::updateAction - methods: [POST, PUT] - -chill_activity_activityreasoncategory_delete: - path: /{_locale}/admin/activityreasoncategory/{id}/delete - controller: Chill\ActivityBundle\Controller\ActivityReasonCategoryController::deleteAction - methods: [POST, DELETE] diff --git a/src/Bundle/ChillActivityBundle/config/services/export.yaml b/src/Bundle/ChillActivityBundle/config/services/export.yaml index 09817d80e..b52911295 100644 --- a/src/Bundle/ChillActivityBundle/config/services/export.yaml +++ b/src/Bundle/ChillActivityBundle/config/services/export.yaml @@ -16,6 +16,18 @@ services: tags: - { name: chill.export, alias: 'list_activity_linked_to_person' } + Chill\ActivityBundle\Export\Export\LinkedToACP\CountPersonsOnActivity: + tags: + - { name: chill.export, alias: 'count_person_on_activity' } + + Chill\ActivityBundle\Export\Export\LinkedToACP\CountHouseholdOnActivity: + tags: + - { name: chill.export, alias: 'count_household_on_activity_acp' } + + Chill\ActivityBundle\Export\Export\LinkedToPerson\CountHouseholdOnActivity: + tags: + - { name: chill.export, alias: 'count_household_on_activity_person' } + chill.activity.export.count_activity_linked_to_acp: class: Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity tags: @@ -53,8 +65,7 @@ services: tags: - { name: chill.export_filter, alias: 'activity_type_filter' } - chill.activity.export.date_filter: - class: Chill\ActivityBundle\Export\Filter\ActivityDateFilter + Chill\ActivityBundle\Export\Filter\ActivityDateFilter: tags: - { name: chill.export_filter, alias: 'activity_date_filter' } @@ -74,55 +85,52 @@ services: name: chill.export_filter alias: 'activity_person_having_ac_bw_date_filter' - chill.activity.export.filter_activitytype: - class: Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter + Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter: tags: - { name: chill.export_filter, alias: 'accompanyingcourse_activitytype_filter' } - chill.activity.export.location_filter: - class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationFilter + Chill\ActivityBundle\Export\Filter\LocationFilter: tags: - { name: chill.export_filter, alias: 'activity_location_filter' } - chill.activity.export.locationtype_filter: - class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter + Chill\ActivityBundle\Export\Filter\LocationTypeFilter: tags: - { name: chill.export_filter, alias: 'activity_locationtype_filter' } - Chill\ActivityBundle\Export\Filter\ACPFilters\ByCreatorFilter: + Chill\ActivityBundle\Export\Filter\ByCreatorFilter: tags: - { name: chill.export_filter, alias: 'activity_bycreator_filter' } - chill.activity.export.emergency_filter: - class: Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter + Chill\ActivityBundle\Export\Filter\EmergencyFilter: tags: - { name: chill.export_filter, alias: 'activity_emergency_filter' } - chill.activity.export.sentreceived_filter: - class: Chill\ActivityBundle\Export\Filter\ACPFilters\SentReceivedFilter + Chill\ActivityBundle\Export\Filter\SentReceivedFilter: tags: - { name: chill.export_filter, alias: 'activity_sentreceived_filter' } - chill.activity.export.bysocialaction_filter: - class: Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter + Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter: tags: - { name: chill.export_filter, alias: 'activity_bysocialaction_filter' } - chill.activity.export.bysocialissue_filter: - class: Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialIssueFilter + Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialIssueFilter: tags: - { name: chill.export_filter, alias: 'activity_bysocialissue_filter' } - chill.activity.export.user_filter: # Creator (M2O) - class: Chill\ActivityBundle\Export\Filter\ACPFilters\UserFilter + Chill\ActivityBundle\Export\Filter\UserFilter: tags: - { name: chill.export_filter, alias: 'activity_user_filter' } - chill.activity.export.userscope_filter: - class: Chill\ActivityBundle\Export\Filter\ACPFilters\UserScopeFilter + Chill\ActivityBundle\Export\Filter\CreatorScopeFilter: tags: + # WARNING: for backward compatibility reason, the alias is named with userscope. Changing this will + # affect all saved exports (unless we write a migration for that) - { name: chill.export_filter, alias: 'activity_userscope_filter' } + Chill\ActivityBundle\Export\Filter\CreatorJobFilter: + tags: + - { name: chill.export_filter, alias: 'activity_creatorjob_filter' } + Chill\ActivityBundle\Export\Filter\UsersJobFilter: tags: - { name: chill.export_filter, alias: 'activity_usersjob_filter' } @@ -135,8 +143,17 @@ services: tags: - { name: chill.export_filter, alias: 'accompanyingcourse_has_no_activity_filter' } + Chill\ActivityBundle\Export\Filter\ACPFilters\PeriodHavingActivityBetweenDatesFilter: + tags: + - { name: chill.export_filter, alias: 'period_having_activity_betw_dates_filter' } + + Chill\ActivityBundle\Export\Filter\ActivityPresenceFilter: + tags: + - { name: chill.export_filter, alias: 'activity_presence_filter' } + + ## Aggregators - Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator: + Chill\ActivityBundle\Export\Aggregator\ActivityReasonAggregator: tags: - { name: chill.export_aggregator, alias: activity_reason_aggregator } @@ -144,27 +161,28 @@ services: tags: - { name: chill.export_aggregator, alias: activity_common_type_aggregator } + Chill\ActivityBundle\Export\Aggregator\ActivityLocationAggregator: + tags: + - { name: chill.export_aggregator, alias: activity_common_location_aggregator } + chill.activity.export.user_aggregator: class: Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator tags: - { name: chill.export_aggregator, alias: activity_user_aggregator } - chill.activity.export.locationtype_aggregator: - class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\LocationTypeAggregator + Chill\ActivityBundle\Export\Aggregator\LocationTypeAggregator: tags: - { name: chill.export_aggregator, alias: activity_locationtype_aggregator } - chill.activity.export.date_aggregator: - class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\DateAggregator + Chill\ActivityBundle\Export\Aggregator\DateAggregator: tags: - { name: chill.export_aggregator, alias: activity_date_aggregator } - Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByCreatorAggregator: + Chill\ActivityBundle\Export\Aggregator\ByCreatorAggregator: tags: - { name: chill.export_aggregator, alias: activity_by_creator_aggregator } - chill.activity.export.bythirdparty_aggregator: - class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator + Chill\ActivityBundle\Export\Aggregator\ByThirdpartyAggregator: tags: - { name: chill.export_aggregator, alias: activity_bythirdparty_aggregator } @@ -178,10 +196,14 @@ services: tags: - { name: chill.export_aggregator, alias: activity_bysocialissue_aggregator } - Chill\ActivityBundle\Export\Aggregator\ACPAggregators\CreatorScopeAggregator: + Chill\ActivityBundle\Export\Aggregator\CreatorScopeAggregator: tags: - { name: chill.export_aggregator, alias: activity_creator_scope_aggregator } + Chill\ActivityBundle\Export\Aggregator\CreatorJobAggregator: + tags: + - { name: chill.export_aggregator, alias: activity_creator_job_aggregator } + Chill\ActivityBundle\Export\Aggregator\ActivityUsersAggregator: tags: - { name: chill.export_aggregator, alias: activity_users_aggregator } @@ -201,3 +223,23 @@ services: Chill\ActivityBundle\Export\Aggregator\SentReceivedAggregator: tags: - { name: chill.export_aggregator, alias: activity_sentreceived_aggregator } + + Chill\ActivityBundle\Export\Filter\PersonsFilter: + tags: + - { name: chill.export_filter, alias: activity_by_persons_filter } + + Chill\ActivityBundle\Export\Aggregator\PersonsAggregator: + tags: + - { name: chill.export_aggregator, alias: activity_by_persons_aggregator } + + Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByActivityTypeAggregator: + tags: + - { name: chill.export_aggregator, alias: acp_by_activity_type_aggregator } + + Chill\ActivityBundle\Export\Aggregator\ActivityPresenceAggregator: + tags: + - { name: chill.export_aggregator, alias: activity_presence_agg } + + Chill\ActivityBundle\Export\Aggregator\PersonAggregators\PersonAggregator: + tags: + - { name: chill.export_aggregator, alias: activity_person_agg } diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20150701091248.php b/src/Bundle/ChillActivityBundle/migrations/Version20150701091248.php index f96762748..5a050035a 100644 --- a/src/Bundle/ChillActivityBundle/migrations/Version20150701091248.php +++ b/src/Bundle/ChillActivityBundle/migrations/Version20150701091248.php @@ -22,7 +22,7 @@ class Version20150701091248 extends AbstractMigration public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('ALTER TABLE Activity DROP CONSTRAINT FK_55026B0C59BB1592'); $this->addSql('ALTER TABLE ActivityReason DROP CONSTRAINT FK_654A2FCD12469DE2'); @@ -40,7 +40,7 @@ class Version20150701091248 extends AbstractMigration public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('CREATE SEQUENCE Activity_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE ActivityReason_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20150702093317.php b/src/Bundle/ChillActivityBundle/migrations/Version20150702093317.php index f8dc40e95..053e4ecc8 100644 --- a/src/Bundle/ChillActivityBundle/migrations/Version20150702093317.php +++ b/src/Bundle/ChillActivityBundle/migrations/Version20150702093317.php @@ -22,7 +22,7 @@ class Version20150702093317 extends AbstractMigration public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('ALTER TABLE ActivityReasonCategory DROP COLUMN name;'); $this->addSql('ALTER TABLE ActivityReasonCategory ADD COLUMN label VARCHAR(255) NOT NULL;'); @@ -35,7 +35,7 @@ class Version20150702093317 extends AbstractMigration public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('ALTER TABLE ActivityReasonCategory DROP COLUMN label;'); $this->addSql('ALTER TABLE ActivityReasonCategory ADD COLUMN name JSON;'); diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20150704091347.php b/src/Bundle/ChillActivityBundle/migrations/Version20150704091347.php index 6509701cc..78204fa67 100644 --- a/src/Bundle/ChillActivityBundle/migrations/Version20150704091347.php +++ b/src/Bundle/ChillActivityBundle/migrations/Version20150704091347.php @@ -22,7 +22,7 @@ class Version20150704091347 extends AbstractMigration public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('ALTER TABLE Activity ALTER COLUMN remark SET NOT NULL;'); $this->addSql('ALTER TABLE Activity ALTER COLUMN attendee DROP NOT NULL;'); @@ -31,7 +31,7 @@ class Version20150704091347 extends AbstractMigration public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('ALTER TABLE Activity ALTER COLUMN remark DROP NOT NULL;'); $this->addSql('ALTER TABLE Activity ALTER COLUMN attendee DROP NOT NULL;'); diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20160222103457.php b/src/Bundle/ChillActivityBundle/migrations/Version20160222103457.php index 1531230c1..ceb0e59e6 100644 --- a/src/Bundle/ChillActivityBundle/migrations/Version20160222103457.php +++ b/src/Bundle/ChillActivityBundle/migrations/Version20160222103457.php @@ -25,14 +25,14 @@ class Version20160222103457 extends AbstractMigration public function down(Schema $schema): void { $this->abortIf( - $this->connection->getDatabasePlatform()->getName() !== 'postgresql', + 'postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.' ); $this->addSql('ALTER TABLE Activity ADD reason_id INT DEFAULT NULL'); $this->addSql('ALTER TABLE Activity ADD CONSTRAINT ' - . 'fk_55026b0c59bb1592 FOREIGN KEY (reason_id) ' - . 'REFERENCES activityreason (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + .'fk_55026b0c59bb1592 FOREIGN KEY (reason_id) ' + .'REFERENCES activityreason (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('CREATE INDEX idx_55026b0c59bb1592 ON Activity (reason_id)'); // try to keep at least on activity reason... @@ -52,29 +52,29 @@ class Version20160222103457 extends AbstractMigration public function up(Schema $schema): void { $this->abortIf( - $this->connection->getDatabasePlatform()->getName() !== 'postgresql', + 'postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.' ); // create the new table activity reason $this->addSql( 'CREATE TABLE activity_activityreason (' - . 'activity_id INT NOT NULL, ' - . 'activityreason_id INT NOT NULL, ' - . 'PRIMARY KEY(activity_id, activityreason_id))' + .'activity_id INT NOT NULL, ' + .'activityreason_id INT NOT NULL, ' + .'PRIMARY KEY(activity_id, activityreason_id))' ); $this->addSql('CREATE INDEX IDX_338A864381C06096 ON activity_activityreason (activity_id)'); $this->addSql('CREATE INDEX IDX_338A8643D771E0FC ON activity_activityreason (activityreason_id)'); $this->addSql('ALTER TABLE activity_activityreason ' - . 'ADD CONSTRAINT FK_338A864381C06096 FOREIGN KEY (activity_id) ' - . 'REFERENCES Activity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + .'ADD CONSTRAINT FK_338A864381C06096 FOREIGN KEY (activity_id) ' + .'REFERENCES Activity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE activity_activityreason ' - . 'ADD CONSTRAINT FK_338A8643D771E0FC FOREIGN KEY (activityreason_id) ' - . 'REFERENCES ActivityReason (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + .'ADD CONSTRAINT FK_338A8643D771E0FC FOREIGN KEY (activityreason_id) ' + .'REFERENCES ActivityReason (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); // migrate old data to new table $this->addSql('INSERT INTO activity_activityreason (activity_id, activityreason_id) ' - . 'SELECT id, reason_id FROM activity WHERE reason_id IS NOT NULL'); + .'SELECT id, reason_id FROM activity WHERE reason_id IS NOT NULL'); // remove old column $this->addSql('ALTER TABLE activity DROP CONSTRAINT fk_55026b0c59bb1592'); diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20210422073711.php b/src/Bundle/ChillActivityBundle/migrations/Version20210422073711.php index ec321faa7..9be59b2b9 100644 --- a/src/Bundle/ChillActivityBundle/migrations/Version20210422073711.php +++ b/src/Bundle/ChillActivityBundle/migrations/Version20210422073711.php @@ -14,8 +14,6 @@ namespace Chill\Migrations\Activity; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; -use function count; - /** * Auto-generated Migration: Please modify to your needs! */ @@ -49,8 +47,8 @@ final class Version20210422073711 extends AbstractMigration 'Domicile erronéee', ]; - for ($i = 1; count($list) >= $i; ++$i) { - $this->addSql('INSERT INTO activitytpresence VALUES(' . $i . ", json_build_object('fr', '" . $list[$i - 1] . "'), true)"); + for ($i = 1; \count($list) >= $i; ++$i) { + $this->addSql('INSERT INTO activitytpresence VALUES('.$i.", json_build_object('fr', '".$list[$i - 1]."'), true)"); } $this->addSql('ALTER TABLE activity ADD emergency BOOLEAN NOT NULL DEFAULT false'); diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20210422123846.php b/src/Bundle/ChillActivityBundle/migrations/Version20210422123846.php index 18711bad0..110ca24b2 100644 --- a/src/Bundle/ChillActivityBundle/migrations/Version20210422123846.php +++ b/src/Bundle/ChillActivityBundle/migrations/Version20210422123846.php @@ -58,7 +58,7 @@ final class Version20210422123846 extends AbstractMigration $this->addSql('ALTER TABLE activity_thirdparty ADD CONSTRAINT FK_C6F0DE0381C06096 FOREIGN KEY (activity_id) REFERENCES activity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE activity_thirdparty ADD CONSTRAINT FK_C6F0DE03C7D3A8E6 FOREIGN KEY (thirdparty_id) REFERENCES chill_3party.third_party (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE activity_document ADD CONSTRAINT FK_78633A7881C06096 FOREIGN KEY (activity_id) REFERENCES activity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - //$this->addSql('ALTER TABLE activity_document ADD CONSTRAINT FK_78633A78C33F7837 FOREIGN KEY (document_id) REFERENCES Document (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('ALTER TABLE activity_document ADD CONSTRAINT FK_78633A78C33F7837 FOREIGN KEY (document_id) REFERENCES Document (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE activity_user ADD CONSTRAINT FK_8E570DDB81C06096 FOREIGN KEY (activity_id) REFERENCES activity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE activity_user ADD CONSTRAINT FK_8E570DDBA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); diff --git a/src/Bundle/ChillActivityBundle/translations/messages+intl-icu.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages+intl-icu.fr.yml new file mode 100644 index 000000000..0807861cc --- /dev/null +++ b/src/Bundle/ChillActivityBundle/translations/messages+intl-icu.fr.yml @@ -0,0 +1,16 @@ +export: + filter: + activity: + course_having_activity_between_date: + Only course having an activity between from and to: Seulement les parcours ayant reçu au moins un échange entre le {from, date, short} et le {to, date, short} + + acp_by_activity_type: + 'acp_containing_at_least_one_activitytypes': >- + Parcours filtrés: uniquement ceux qui contiennent au moins un échange d'un des types suivants: {activitytypes} + {has_date_after, select, 1 {, après le {date_after, date}} other {}} + {has_date_before, select, 1 {, avant le {date_before, date}} other {}} + describe_action_with_no_subject: >- + Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date} + describe_action_with_subject: >- + Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date}, et un de ces sujets choisis: {reasons} + diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 55d755fd6..7e72bc016 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -77,18 +77,38 @@ Choose a type: Choisir un type 4 hours: 4 heures 4 hours 30: 4 heures 30 5 hours: 5 heures +5 hours 30: 5 heure 30 +6 hours: 6 heures +6 hours 30: 6 heure 30 +7 hours: 7 heures +7 hours 30: 7 heure 30 +8 hours: 8 heures +8 hours 30: 8 heure 30 +9 hours: 9 heures +9 hours 30: 9 heure 30 +10 hours: 10 heures +11 hours: 11 heures +12 hours: 12 heures Concerned groups: Parties concernées par l'échange Persons in accompanying course: Usagers du parcours Third persons: Tiers non-pro. Others persons: Usagers Third parties: Tiers professionnels Users concerned: T(M)S + activity: + date: Date de l'échange Insert a document: Insérer un document Remove a document: Supprimer le document comment: Commentaire No documents: Aucun document +# activity filter in list page +activity_filter: + My activities: Mes échanges (où j'interviens) + Types: Par type d'échange + Jobs: Par métier impliqué + #timeline '%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"' @@ -248,11 +268,6 @@ Activities before this date: Échanges avant cette date "Filtered by date of activity: only between %date_from% and %date_to%": "Filtré par date de l'échange: uniquement entre %date_from% et %date_to%" This date should be after the date given in "Implied in an activity after this date" field: Cette date devrait être postérieure à la date donnée dans le champ "échanges après cette date" -Filtered by person having an activity in a period: Uniquement les usagers ayant eu un échange dans la période donnée -Implied in an activity after this date: Impliqué dans un échange après cette date -Implied in an activity before this date: Impliqué dans un échange avant cette date -Filtered by person having an activity between %date_from% and %date_to% with reasons %reasons_name%: Filtré par usager associées à un échange entre %date_from% et %date_to% avec les sujets %reasons_name% -Activity reasons for those activities: Sujets de ces échanges Filter by activity type: Filtrer les échanges par type @@ -273,15 +288,13 @@ Filter activity by linked socialaction: Filtrer les échanges par action liée 'Filtered activity by linked socialaction: only %actions%': "Filtré par action liée: uniquement %actions%" Filter activity by linked socialissue: Filtrer les échanges par problématique liée 'Filtered activity by linked socialissue: only %issues%': "Filtré par problématique liée: uniquement %issues%" -Filter activity by user: Filtrer les échanges par créateur +Filter activity by user: Filtrer les échanges par utilisateur principal Filter activity by users: Filtrer les échanges par utilisateur participant Filter activity by creator: Filtrer les échanges par créateur de l'échange 'Filtered activity by user: only %users%': "Filtré par référent: uniquement %users%" 'Filtered activity by users: only %users%': "Filtré par utilisateurs participants: uniquement %users%" 'Filtered activity by creator: only %users%': "Filtré par créateur: uniquement %users%" Creators: Créateurs -Filter activity by userscope: Filtrer les échanges par service du créateur -'Filtered activity by userscope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%" Accepted userscope: Services Filter acp which has no activity: Filtrer les parcours qui n’ont pas d’échange @@ -299,10 +312,6 @@ Aggregate by activity user: Grouper les échanges par référent Aggregate by activity users: Grouper les échanges par utilisateurs participants Aggregate by activity type: Grouper les échanges par type Aggregate by activity reason: Grouper les échanges par sujet -Aggregate by users scope: Grouper les échanges par service principal de l'utilisateur -Users 's scope: Service principal des utilisateurs participants à l'échange -Aggregate by users job: Grouper les échanges par métier des utilisateurs participants -Users 's job: Métier des utilisateurs participants à l'échange Group activity by locationtype: Grouper les échanges par type de localisation Group activity by date: Grouper les échanges par date @@ -313,7 +322,6 @@ for week: Semaine by year: Par année in year: En Group activity by creator: Grouper les échanges par créateur de l'échange -Group activity by creator scope: Grouper les échanges par service du créateur de l'échange Group activity by linked thirdparties: Grouper les échanges par tiers impliqué Accepted thirdparty: Tiers impliqué Group activity by linked socialaction: Grouper les échanges par action liée @@ -337,6 +345,19 @@ docgen: myWorksOnly: Prendre en compte uniquement les actions d'accompagnement dont je suis référent export: + export: + count_person_on_activity: + title: Nombre d'usagers concernés par les échanges + description: Compte le nombre d'usagers concernés par les échanges. Si un usager est présent dans plusieurs échanges, il n'est comptabilisé qu'une seule fois. + header: Nombre d'usagers concernés par des échanges + count_household_on_activity: + title: Nombre de ménages concernés par les échanges + description: Compte le nombre de ménages concernés par les échanges. Si un ménage est présent dans plusieurs échanges, il n'est comptabilisé qu'une seule fois. Les usagers sans ménages ne sont pas comptabilisés. + header: Nombre de ménage concernés par des échanges + count_household_on_activity_person: + title: Nombre de ménages concernés par les échanges + description: Compte le nombre de ménages concernés par les échanges. Si un ménage est présent dans plusieurs échanges, il n'est comptabilisé qu'une seule fois. Les usagers sans ménages ne sont pas comptabilisés. Lorsqu'un usager change de ménage, chaque ménage est comptabilisé une fois. + header: Nombre de ménage concernés par des échanges list: activity: users name: Nom des utilisateurs @@ -360,19 +381,86 @@ export: filter: activity: - by_usersjob: + by_users_job: Filter by users job: Filtrer les échanges par métier d'au moins un utilisateur participant 'Filtered activity by users job: only %jobs%': 'Filtré par métier d''au moins un utilisateur participant: seulement %jobs%' - by_usersscope: - Filter by users scope: Filtrer les échanges par services d'au moins un utilisateur participant + by_users_scope: + Filter by users scope: Filtrer les échanges par service d'au moins un utilisateur participant 'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%' + course_having_activity_between_date: + Title: Filtrer les parcours ayant reçu un échange entre deux dates + Receiving an activity after: Ayant reçu un échange après le + Receiving an activity before: Ayant reçu un échange avant le + acp_by_activity_type: + 'activity after': Échanges après le + activity after help: Si laissé vide, ne sera pas pris en compte + activity before: Echanges avant le + activity before help: Si laissé vide, ne sera pas pris en compte + person_between_dates: + Implied in an activity after this date: Impliqué dans un échange après cette date + Implied in an activity before this date: Impliqué dans un échange avant cette date + Activity reasons for those activities: Sujets de ces échanges + if no reasons: Si aucun sujet n'est coché, tous les sujets seront pris en compte + title: Filtrer les usagers ayant été associés à un échange au cours de la période + date mismatch: La date de fin de la période doit être supérieure à la date du début + by_creator_scope: + Filter activity by user scope: Filtrer les échanges par service du créateur de l'échange + 'Filtered activity by user scope: only %scopes%': "Filtré par service du créateur de l'échange: uniquement %scopes%" + by_creator_job: + job_form_label: Métiers + Filter activity by user job: Filtrer les échanges par métier du créateur de l'échange + 'Filtered activity by user job: only %jobs%': "Filtré par métier du créateur de l'échange: uniquement %jobs%" + by_persons: + Filter activity by persons: Filtrer les échanges par usager participant + 'Filtered activity by persons: only %persons%': 'Échanges filtrés par usagers participants: seulement %persons%' + persons taking part on the activity: Usagers participants à l'échange + by_sent_received: + Sent or received: Envoyé ou reçu + is sent: envoyé + is received: reçu + by_presence: + Filter activity by activity presence: Filtrer les échanges par présence de l'usager + presences: Présences + 'Filtered by activity presence: only %presences%': 'Filtré par présence de l''usager: seulement %presences%' + aggregator: + person: + by_person: + title: Grouper les échanges par usager (dossier d'usager dans lequel l'échange est enregistré) + person: Usager + acp: + by_activity_type: + title: Grouper les parcours par type d'échange + after_date: Uniquement échanges après cette date + before_date: Uniquement échanges avant cette date + activity_type: Types d'échange activity: by_sent_received: Sent or received: Envoyé ou reçu is sent: envoyé is received: reçu Group activity by sentreceived: Grouper les échanges par envoyé / reçu + by_location: + Activity Location: Localisation de l'échange + Title: Grouper les échanges par localisation de l'échange + by_user_job: + Users 's job: Métier des utilisateurs participants à l'échange + Aggregate by users job: Grouper les échanges par métier des utilisateurs participants + by_user_scope: + Users 's scope: Service principal des utilisateurs participants à l'échange + Aggregate by users scope: Grouper les échanges par service principal de l'utilisateur + by_creator_scope: + Group activity by creator scope: Grouper les échanges par service du créateur de l'échange + Calc date: Date de calcul du service du créateur de l'échange + by_creator_job: + Group activity by creator job: Grouper les échanges par métier du créateur de l'échange + Calc date: Date de calcul du métier du créateur de l'échange + by_persons: + Group activity by persons: Grouper les échanges par usager participant + Persons: Usagers participants + by_activity_presence: + Group activity by presence: Grouper les échanges par présence de l'usager + header: Présence de(s) usager(s) generic_doc: filter: diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php index 9144bb129..cbf0ba9ec 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php @@ -24,6 +24,6 @@ class AdminController extends AbstractController */ public function indexAdminAction() { - return $this->render('ChillAsideActivityBundle:Admin:index.html.twig'); + return $this->render('@ChillAsideActivity/Admin/index.html.twig'); } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityCategoryController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityCategoryController.php index 009a9f2c8..602ef1204 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityCategoryController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityCategoryController.php @@ -23,7 +23,7 @@ class AsideActivityCategoryController extends CRUDController { protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { - /** @var QueryBuilder $query */ + /* @var QueryBuilder $query */ $query->addOrderBy('e.ordering', 'ASC'); return $query; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php index 8481f24eb..6bd2affe7 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php @@ -16,16 +16,12 @@ use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository; use Chill\MainBundle\CRUD\Controller\CRUDController; use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Templating\Listing\FilterOrderHelper; -use DateTime; use Symfony\Component\HttpFoundation\Request; final class AsideActivityController extends CRUDController { - private AsideActivityCategoryRepository $categoryRepository; - - public function __construct(AsideActivityCategoryRepository $categoryRepository) + public function __construct(private readonly AsideActivityCategoryRepository $categoryRepository) { - $this->categoryRepository = $categoryRepository; } public function createEntity(string $action, Request $request): object @@ -33,9 +29,10 @@ final class AsideActivityController extends CRUDController $asideActivity = new AsideActivity(); $asideActivity->setAgent($this->getUser()); + $asideActivity->setLocation($this->getUser()->getCurrentLocation()); $duration = $request->query->get('duration', '300'); - $duration = DateTime::createFromFormat('U', $duration); + $duration = \DateTime::createFromFormat('U', $duration); $asideActivity->setDuration($duration); $categoryId = $request->query->get('type', 7); diff --git a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php index 6f6a11390..7bc6b774f 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php @@ -14,21 +14,14 @@ namespace Chill\AsideActivityBundle\DataFixtures\ORM; use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\MainBundle\DataFixtures\ORM\LoadUsers; use Chill\MainBundle\Repository\UserRepository; -use DateInterval; -use DateTimeImmutable; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Persistence\ObjectManager; -use function random_int; - class LoadAsideActivity extends Fixture implements DependentFixtureInterface { - private UserRepository $userRepository; - - public function __construct(UserRepository $userRepository) + public function __construct(private readonly UserRepository $userRepository) { - $this->userRepository = $userRepository; } public function getDependencies(): array @@ -47,15 +40,15 @@ class LoadAsideActivity extends Fixture implements DependentFixtureInterface $activity = new AsideActivity(); $activity ->setAgent($user) - ->setCreatedAt(new DateTimeImmutable('now')) + ->setCreatedAt(new \DateTimeImmutable('now')) ->setCreatedBy($user) - ->setUpdatedAt(new DateTimeImmutable('now')) + ->setUpdatedAt(new \DateTimeImmutable('now')) ->setUpdatedBy($user) ->setType( $this->getReference('aside_activity_category_0') ) - ->setDate((new DateTimeImmutable('today')) - ->sub(new DateInterval('P' . random_int(1, 100) . 'D'))); + ->setDate((new \DateTimeImmutable('today')) + ->sub(new \DateInterval('P'.\random_int(1, 100).'D'))); $manager->persist($activity); } diff --git a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php index 1d15adbc1..3e6b4247a 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php @@ -27,7 +27,7 @@ class LoadAsideActivityCategory extends \Doctrine\Bundle\FixturesBundle\Fixture $category = new AsideActivityCategory(); $category->setTitle(['fr' => $label]); $manager->persist($category); - $this->setReference('aside_activity_category_' . $key, $category); + $this->setReference('aside_activity_category_'.$key, $category); } $manager->flush(); diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php index 927de1845..056f29ba1 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php @@ -26,7 +26,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte $container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services.yaml'); $loader->load('services/form.yaml'); $loader->load('services/menu.yaml'); @@ -100,7 +100,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte protected function prependRoute(ContainerBuilder $container) { - //declare routes for task bundle + // declare routes for task bundle $container->prependExtensionConfig('chill_main', [ 'routing' => [ 'resources' => [ diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php index 2e2398c3e..241a545a8 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php @@ -14,8 +14,6 @@ namespace Chill\AsideActivityBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; -use function is_int; - class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() @@ -132,7 +130,7 @@ class Configuration implements ConfigurationInterface ->info('The number of seconds of this duration. Must be an integer.') ->cannotBeEmpty() ->validate() - ->ifTrue(static fn ($data) => !is_int($data))->thenInvalid('The value %s is not a valid integer') + ->ifTrue(static fn ($data) => !\is_int($data))->thenInvalid('The value %s is not a valid integer') ->end() ->end() ->scalarNode('label') diff --git a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php index 3dc205f09..f43a0fdfb 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php @@ -14,83 +14,92 @@ namespace Chill\AsideActivityBundle\Entity; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Entity\User; -use DateTimeInterface; +use Chill\MainBundle\Entity\Location; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity + * * @ORM\Table(schema="chill_asideactivity") */ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface { /** * @ORM\ManyToOne(targetEntity=User::class) + * * @ORM\JoinColumn(nullable=false) + * * @Assert\NotBlank */ - private \Chill\MainBundle\Entity\User $agent; + private User $agent; /** * @ORM\Column(type="datetime") */ - private $createdAt; + private ?\DateTimeInterface $createdAt = null; /** * @ORM\ManyToOne(targetEntity=User::class) + * * @ORM\JoinColumn(nullable=false) */ - private \Chill\MainBundle\Entity\User $createdBy; + private User $createdBy; /** * @ORM\Column(type="datetime") */ - private $date; + private ?\DateTimeInterface $date = null; /** * @ORM\Column(type="time", nullable=true) */ - private ?DateTimeInterface $duration = null; + private ?\DateTimeInterface $duration = null; /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") */ private ?int $id = null; /** - * @ORM\Column(type="string", length=100, nullable=true) + * @ORM\ManyToOne(targetEntity=Location::class) + * + * @ORM\JoinColumn(nullable=true) */ - private $location; + private ?Location $location = null; /** * @ORM\Column(type="text", nullable=true) */ - private $note; + private ?string $note = null; /** * @ORM\ManyToOne(targetEntity=AsideActivityCategory::class, inversedBy="asideActivities") + * * @ORM\JoinColumn(nullable=false) */ - private $type; + private ?AsideActivityCategory $type = null; /** * @ORM\Column(type="datetime", nullable=true) */ - private $updatedAt; + private ?\DateTimeInterface $updatedAt = null; /** * @ORM\ManyToOne(targetEntity=User::class) */ - private \Chill\MainBundle\Entity\User $updatedBy; + private User $updatedBy; public function getAgent(): ?User { return $this->agent; } - public function getCreatedAt(): ?DateTimeInterface + public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; } @@ -100,12 +109,12 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface return $this->createdBy; } - public function getDate(): ?DateTimeInterface + public function getDate(): ?\DateTimeInterface { return $this->date; } - public function getDuration(): ?DateTimeInterface + public function getDuration(): ?\DateTimeInterface { return $this->duration; } @@ -115,7 +124,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface return $this->id; } - public function getLocation(): ?string + public function getLocation(): ?Location { return $this->location; } @@ -130,7 +139,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface return $this->type; } - public function getUpdatedAt(): ?DateTimeInterface + public function getUpdatedAt(): ?\DateTimeInterface { return $this->updatedAt; } @@ -147,7 +156,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function setCreatedAt(DateTimeInterface $createdAt): self + public function setCreatedAt(\DateTimeInterface $createdAt): self { $this->createdAt = $createdAt; @@ -161,21 +170,21 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function setDate(DateTimeInterface $date): self + public function setDate(\DateTimeInterface $date): self { $this->date = $date; return $this; } - public function setDuration(?DateTimeInterface $duration): self + public function setDuration(?\DateTimeInterface $duration): self { $this->duration = $duration; return $this; } - public function setLocation(?string $location): self + public function setLocation(?Location $location): self { $this->location = $location; @@ -196,7 +205,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function setUpdatedAt(DateTimeInterface $updatedAt): self + public function setUpdatedAt(\DateTimeInterface $updatedAt): self { $this->updatedAt = $updatedAt; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php index 6c12e5828..be71d73f9 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php @@ -19,24 +19,28 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @ORM\Entity + * * @ORM\Table(schema="chill_asideactivity") */ class AsideActivityCategory { /** * @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent") + * + * @var Collection */ - private $children; + private Collection $children; /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") */ private int $id; /** - * @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent") * @ORM\Column(type="boolean") */ private bool $isActive = true; @@ -50,6 +54,7 @@ class AsideActivityCategory /** * @ORM\ManyToOne(targetEntity=AsideActivityCategory::class, inversedBy="children") + * * @ORM\JoinColumn(nullable=true) */ private ?AsideActivityCategory $parent = null; @@ -114,10 +119,8 @@ class AsideActivityCategory /** * @Assert\Callback - * - * @param mixed $payload */ - public function preventRecursiveParent(ExecutionContextInterface $context, $payload) + public function preventRecursiveParent(ExecutionContextInterface $context, mixed $payload) { if (!$this->hasParent()) { return; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php index 8ae43534d..43b702f5c 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php @@ -20,14 +20,10 @@ use Symfony\Component\Form\FormBuilderInterface; class ByActivityTypeAggregator implements AggregatorInterface { - private AsideActivityCategoryRepository $asideActivityCategoryRepository; - - private TranslatableStringHelper $translatableStringHelper; - - public function __construct(AsideActivityCategoryRepository $asideActivityCategoryRepository, TranslatableStringHelper $translatableStringHelper) - { - $this->asideActivityCategoryRepository = $asideActivityCategoryRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly AsideActivityCategoryRepository $asideActivityCategoryRepository, + private readonly TranslatableStringHelper $translatableStringHelper + ) { } public function addRole(): ?string @@ -50,6 +46,7 @@ class ByActivityTypeAggregator implements AggregatorInterface { // No form needed } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php new file mode 100644 index 000000000..095d20acb --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php @@ -0,0 +1,75 @@ +locationRepository->find($value)) { + return ''; + } + + return $l->getName(); + }; + } + + public function getQueryKeys($data): array + { + return ['by_aside_activity_location_aggregator']; + } + + public function getTitle(): string + { + return 'export.aggregator.Group by aside activity location'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data): void + { + $qb->addSelect('IDENTITY(aside.location) AS by_aside_activity_location_aggregator') + ->addGroupBy('by_aside_activity_location_aggregator'); + } + + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php index f09a704e2..94d946907 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php @@ -12,24 +12,22 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Export\Aggregator; use Chill\AsideActivityBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; - class ByUserJobAggregator implements AggregatorInterface { - private TranslatableStringHelperInterface $translatableStringHelper; + private const PREFIX = 'aside_act_agg_user_job'; - private UserJobRepositoryInterface $userJobRepository; - - public function __construct(UserJobRepositoryInterface $userJobRepository, TranslatableStringHelperInterface $translatableStringHelper) - { - $this->userJobRepository = $userJobRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly UserJobRepositoryInterface $userJobRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) { } public function addRole(): ?string @@ -39,24 +37,39 @@ class ByUserJobAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('aside_user', $qb->getAllAliases(), true)) { - $qb->leftJoin('aside.agent', 'aside_user'); - } + $p = self::PREFIX; $qb - ->addSelect('IDENTITY(aside_user.userJob) AS aside_activity_user_job_aggregator') - ->addGroupBy('aside_activity_user_job_aggregator'); + ->leftJoin('aside.agent', "{$p}_user") + ->leftJoin( + UserJobHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // job_at based on aside.date + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'aside.date'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'aside.date') + ) + ) + ) + ->addSelect("IDENTITY({$p}_history.job) AS {$p}_select") + ->addGroupBy("{$p}_select"); } - public function applyOn() + public function applyOn(): string { return Declarations::ASIDE_ACTIVITY_TYPE; } public function buildForm(FormBuilderInterface $builder) { - // nothing to add in the form } + public function getFormDefaultData(): array { return []; @@ -83,11 +96,11 @@ class ByUserJobAggregator implements AggregatorInterface public function getQueryKeys($data): array { - return ['aside_activity_user_job_aggregator']; + return [self::PREFIX.'_select']; } - public function getTitle() + public function getTitle(): string { - return 'export.aggregator.Aggregate by user job'; + return 'export.aggregator.by_user_job.Aggregate by user job'; } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php index 3dd465db2..90e4ee615 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php @@ -12,24 +12,22 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Export\Aggregator; use Chill\AsideActivityBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; - class ByUserScopeAggregator implements AggregatorInterface { - private ScopeRepositoryInterface $scopeRepository; + private const PREFIX = 'aside_act_agg_user_scope'; - private TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct(ScopeRepositoryInterface $scopeRepository, TranslatableStringHelperInterface $translatableStringHelper) - { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly ScopeRepositoryInterface $scopeRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) { } public function addRole(): ?string @@ -39,24 +37,38 @@ class ByUserScopeAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('aside_user', $qb->getAllAliases(), true)) { - $qb->leftJoin('aside.agent', 'aside_user'); - } + $p = self::PREFIX; $qb - ->addSelect('IDENTITY(aside_user.mainScope) AS aside_activity_user_scope_aggregator') - ->addGroupBy('aside_activity_user_scope_aggregator'); + ->leftJoin('aside.agent', "{$p}_user") + ->leftJoin( + UserScopeHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'aside.date'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'aside.date') + ) + ) + ) + ->addSelect("IDENTITY({$p}_history.scope) AS {$p}_select") + ->addGroupBy("{$p}_select"); } - public function applyOn() + public function applyOn(): string { return Declarations::ASIDE_ACTIVITY_TYPE; } public function buildForm(FormBuilderInterface $builder) { - // nothing to add in the form } + public function getFormDefaultData(): array { return []; @@ -83,11 +95,11 @@ class ByUserScopeAggregator implements AggregatorInterface public function getQueryKeys($data): array { - return ['aside_activity_user_scope_aggregator']; + return [self::PREFIX.'_select']; } - public function getTitle() + public function getTitle(): string { - return 'export.aggregator.Aggregate by user scope'; + return 'export.aggregator.by_user_scope.Aggregate by user scope'; } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Declarations.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Declarations.php index 8c808ea37..f86f78866 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Declarations.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Declarations.php @@ -16,5 +16,5 @@ namespace Chill\AsideActivityBundle\Export; */ abstract class Declarations { - public const ASIDE_ACTIVITY_TYPE = 'aside_activity'; + final public const ASIDE_ACTIVITY_TYPE = 'aside_activity'; } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php index 2b28062f6..f4afd9181 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php @@ -18,22 +18,18 @@ use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Doctrine\ORM\Query; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterface { - private AsideActivityRepository $repository; - - public function __construct( - AsideActivityRepository $repository - ) { - $this->repository = $repository; + public function __construct(private readonly AsideActivityRepository $repository) + { } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -57,7 +53,7 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac public function getLabels($key, array $values, $data) { if ('export_avg_aside_activity_duration' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } return static fn ($value) => '_header' === $value ? 'Average duration aside activities' : $value; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php index 91210f764..b8f3101b7 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php @@ -18,22 +18,18 @@ use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Doctrine\ORM\Query; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class CountAsideActivity implements ExportInterface, GroupedExportInterface { - private AsideActivityRepository $repository; - - public function __construct( - AsideActivityRepository $repository - ) { - $this->repository = $repository; + public function __construct(private readonly AsideActivityRepository $repository) + { } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -57,7 +53,7 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { if ('export_result' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } $labels = array_combine($values, $values); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php index b46e120b5..37519b559 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php @@ -23,56 +23,32 @@ use Chill\MainBundle\Export\Helper\DateTimeHelper; use Chill\MainBundle\Export\Helper\UserHelper; use Chill\MainBundle\Export\ListInterface; use Chill\MainBundle\Repository\CenterRepositoryInterface; +use Chill\MainBundle\Repository\LocationRepository; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; -use DateTimeInterface; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\QueryBuilder; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; -final class ListAsideActivity implements ListInterface, GroupedExportInterface +final readonly class ListAsideActivity implements ListInterface, GroupedExportInterface { - private AsideActivityCategoryRepository $asideActivityCategoryRepository; - - private CategoryRender $categoryRender; - - private CenterRepositoryInterface $centerRepository; - - private DateTimeHelper $dateTimeHelper; - - private EntityManagerInterface $em; - - private ScopeRepositoryInterface $scopeRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; - - private UserHelper $userHelper; - public function __construct( - EntityManagerInterface $em, - DateTimeHelper $dateTimeHelper, - UserHelper $userHelper, - ScopeRepositoryInterface $scopeRepository, - CenterRepositoryInterface $centerRepository, - AsideActivityCategoryRepository $asideActivityCategoryRepository, - CategoryRender $categoryRender, - TranslatableStringHelperInterface $translatableStringHelper + private EntityManagerInterface $em, + private DateTimeHelper $dateTimeHelper, + private UserHelper $userHelper, + private ScopeRepositoryInterface $scopeRepository, + private CenterRepositoryInterface $centerRepository, + private AsideActivityCategoryRepository $asideActivityCategoryRepository, + private CategoryRender $categoryRender, + private LocationRepository $locationRepository, + private TranslatableStringHelperInterface $translatableStringHelper ) { - $this->em = $em; - $this->dateTimeHelper = $dateTimeHelper; - $this->userHelper = $userHelper; - $this->scopeRepository = $scopeRepository; - $this->centerRepository = $centerRepository; - $this->asideActivityCategoryRepository = $asideActivityCategoryRepository; - $this->categoryRender = $categoryRender; - $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -95,86 +71,78 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { - switch ($key) { - case 'id': - case 'note': - return static function ($value) use ($key) { - if ('_header' === $value) { - return 'export.aside_activity.' . $key; - } + return match ($key) { + 'id', 'note' => static function ($value) use ($key) { + if ('_header' === $value) { + return 'export.aside_activity.'.$key; + } - return $value ?? ''; - }; + return $value ?? ''; + }, + 'duration' => static function ($value) use ($key) { + if ('_header' === $value) { + return 'export.aside_activity.'.$key; + } - case 'duration': - return static function ($value) use ($key) { - if ('_header' === $value) { - return 'export.aside_activity.' . $key; - } + if (null === $value) { + return ''; + } - if (null === $value) { - return ''; - } + if ($value instanceof \DateTimeInterface) { + return $value->format('H:i:s'); + } - if ($value instanceof DateTimeInterface) { - return $value->format('H:i:s'); - } + return $value; + }, + 'createdAt', 'updatedAt', 'date' => $this->dateTimeHelper->getLabel('export.aside_activity.'.$key), + 'agent_id', 'creator_id' => $this->userHelper->getLabel($key, $values, 'export.aside_activity.'.$key), + 'aside_activity_type' => function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.aside_activity_type'; + } - return $value; - }; + if (null === $value || '' === $value || null === $c = $this->asideActivityCategoryRepository->find($value)) { + return ''; + } - case 'createdAt': - case 'updatedAt': - case 'date': - return $this->dateTimeHelper->getLabel('export.aside_activity.' . $key); + return $this->categoryRender->renderString($c, []); + }, + 'location' => function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.location'; + } - case 'agent_id': - case 'creator_id': - return $this->userHelper->getLabel($key, $values, 'export.aside_activity.' . $key); + if (null === $value || '' === $value || null === $l = $this->locationRepository->find($value)) { + return ''; + } - case 'aside_activity_type': - return function ($value) { - if ('_header' === $value) { - return 'export.aside_activity.aside_activity_type'; - } + return $l->getName(); + }, + 'main_scope' => function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.main_scope'; + } - if (null === $value || '' === $value || null === $c = $this->asideActivityCategoryRepository->find($value)) { - return ''; - } + if (null === $value || '' === $value || null === $c = $this->scopeRepository->find($value)) { + return ''; + } - return $this->categoryRender->renderString($c, []); - }; + return $this->translatableStringHelper->localize($c->getName()); + }, + 'main_center' => function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.main_center'; + } - case 'main_scope': - return function ($value) { - if ('_header' === $value) { - return 'export.aside_activity.main_scope'; - } + if (null === $value || '' === $value || null === $c = $this->centerRepository->find($value)) { + /* @var Center $c */ + return ''; + } - if (null === $value || '' === $value || null === $c = $this->scopeRepository->find($value)) { - return ''; - } - - return $this->translatableStringHelper->localize($c->getName()); - }; - - case 'main_center': - return function ($value) { - if ('_header' === $value) { - return 'export.aside_activity.main_center'; - } - - if (null === $value || '' === $value || null === $c = $this->centerRepository->find($value)) { - /** @var Center $c */ - return ''; - } - - return $c->getName(); - }; - - default: - throw new LogicException('this key is not supported : ' . $key); - } + return $c->getName(); + }, + default => throw new \LogicException('this key is not supported : '.$key), + }; } public function getQueryKeys($data) @@ -191,6 +159,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface 'date', 'duration', 'note', + 'location', ]; } @@ -213,7 +182,10 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface { $qb = $this->em->createQueryBuilder() ->from(AsideActivity::class, 'aside') - ->leftJoin('aside.agent', 'agent'); + ->leftJoin('aside.agent', 'agent') + ->leftJoin('agent.scopeHistories', 'scopeHistories') + ->andWhere('scopeHistories.startDate <= aside.date AND (scopeHistories.endDate IS NULL or scopeHistories.endDate > aside.date)') + ; $qb ->addSelect('aside.id AS id') @@ -221,11 +193,12 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface ->addSelect('aside.updatedAt AS updatedAt') ->addSelect('IDENTITY(aside.agent) AS agent_id') ->addSelect('IDENTITY(aside.createdBy) AS creator_id') - ->addSelect('IDENTITY(agent.mainScope) AS main_scope') + ->addSelect('IDENTITY(scopeHistories.scope) AS main_scope') ->addSelect('IDENTITY(agent.mainCenter) AS main_center') ->addSelect('IDENTITY(aside.type) AS aside_activity_type') ->addSelect('aside.date') ->addSelect('aside.duration') + ->addSelect('IDENTITY(aside.location) AS location') ->addSelect('aside.note'); return $qb; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php index 741f129f1..872d7305c 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php @@ -18,22 +18,18 @@ use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Doctrine\ORM\Query; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class SumAsideActivityDuration implements ExportInterface, GroupedExportInterface { - private AsideActivityRepository $repository; - - public function __construct( - AsideActivityRepository $repository - ) { - $this->repository = $repository; + public function __construct(private readonly AsideActivityRepository $repository) + { } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -57,7 +53,7 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac public function getLabels($key, array $values, $data) { if ('export_sum_aside_activity_duration' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } return static fn ($value) => '_header' === $value ? 'Sum duration aside activities' : $value; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php index 3d389f5b3..7c1f4348c 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php @@ -17,26 +17,18 @@ use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository; use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; class ByActivityTypeFilter implements FilterInterface { - private AsideActivityCategoryRepository $asideActivityTypeRepository; - - private CategoryRender $categoryRender; - - private TranslatableStringHelperInterface $translatableStringHelper; - public function __construct( - CategoryRender $categoryRender, - TranslatableStringHelperInterface $translatableStringHelper, - AsideActivityCategoryRepository $asideActivityTypeRepository + private readonly CategoryRender $categoryRender, + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly AsideActivityCategoryRepository $asideActivityTypeRepository ) { - $this->categoryRender = $categoryRender; - $this->asideActivityTypeRepository = $asideActivityTypeRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -76,6 +68,7 @@ class ByActivityTypeFilter implements FilterInterface }, ]); } + public function getFormDefaultData(): array { return []; @@ -85,7 +78,7 @@ class ByActivityTypeFilter implements FilterInterface { $types = array_map( fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getTitle()), - $data['types']->toArray() + $data['types'] instanceof Collection ? $data['types']->toArray() : $data['types'] ); return ['export.filter.Filtered by aside activity type: only %type%', [ diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php index 98a254bc4..703190f74 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php @@ -13,29 +13,17 @@ namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Export\Declarations; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\Export\FilterType; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormError; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; use Symfony\Contracts\Translation\TranslatorInterface; class ByDateFilter implements FilterInterface { - protected TranslatorInterface $translator; - - private RollingDateConverterInterface $rollingDateConverter; - - public function __construct( - RollingDateConverterInterface $rollingDateConverter, - TranslatorInterface $translator - ) { - $this->translator = $translator; - $this->rollingDateConverter = $rollingDateConverter; + public function __construct(private readonly RollingDateConverterInterface $rollingDateConverter, protected TranslatorInterface $translator) + { } public function addRole(): ?string @@ -76,50 +64,14 @@ class ByDateFilter implements FilterInterface ->add('date_to', PickRollingDateType::class, [ 'label' => 'export.filter.Aside activities before this date', ]); - - $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { - /** @var \Symfony\Component\Form\FormInterface $filterForm */ - $filterForm = $event->getForm()->getParent(); - $enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData(); - - if (true === $enabled) { - // if the filter is enabled, add some validation - $form = $event->getForm(); - $date_from = $form->get('date_from')->getData(); - $date_to = $form->get('date_to')->getData(); - - // check that fields are not empty - if (null === $date_from) { - $form->get('date_from')->addError(new FormError( - $this->translator->trans('This field ' - . 'should not be empty') - )); - } - - if (null === $date_to) { - $form->get('date_to')->addError(new FormError( - $this->translator->trans('This field ' - . 'should not be empty') - )); - } - - // check that date_from is before date_to - if ( - (null !== $date_from && null !== $date_to) - && $date_from >= $date_to - ) { - $form->get('date_to')->addError(new FormError( - $this->translator->trans('export.filter.This date should be after ' - . 'the date given in "Implied in an aside activity after ' - . 'this date" field') - )); - } - } - }); } + public function getFormDefaultData(): array { - return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; + return [ + 'date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'date_to' => new RollingDate(RollingDate::T_TODAY), + ]; } public function describeAction($data, $format = 'string'): array diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php new file mode 100644 index 000000000..20e8c46f8 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php @@ -0,0 +1,88 @@ +add('locations', PickUserLocationType::class); + } + + public function getFormDefaultData(): array + { + $user = $this->security->getUser(); + + if ($user instanceof User) { + return [ + 'locations' => $user->getCurrentLocation(), + ]; + } + + return [ + 'locations' => null, + ]; + } + + public function describeAction($data, $format = 'string'): array + { + $extractFunction = fn (Location $l): string => $l->getName(); + if ($data['locations'] instanceof Collection) { + $locations = $data['locations']->map($extractFunction); + } else { + $locations = array_map($extractFunction, $data['locations']); + } + + return ['export.filter.Filtered by aside activity location: only %location%', [ + '%location%' => implode(', ', $locations), + ]]; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data): void + { + $clause = $qb->expr()->in('aside.location', ':locations'); + + $qb->andWhere($clause); + $qb->setParameter('locations', $data['locations']); + } + + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php index 2858d3417..e39633914 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php @@ -20,11 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface; class ByUserFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) + public function __construct(private readonly UserRender $userRender) { - $this->userRender = $userRender; } public function addRole(): ?string @@ -53,6 +50,7 @@ class ByUserFilter implements FilterInterface 'label' => 'Creators', ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php index 55f477768..691e708c6 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php @@ -13,20 +13,24 @@ namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; class ByUserJobFilter implements FilterInterface { - private TranslatableStringHelperInterface $translatableStringHelper; + private const PREFIX = 'aside_act_filter_user_job'; - public function __construct(TranslatableStringHelperInterface $translatableStringHelper) - { - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly UserJobRepositoryInterface $userJobRepository + ) { } public function addRole(): ?string @@ -36,50 +40,66 @@ class ByUserJobFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { + $p = self::PREFIX; + $qb ->andWhere( $qb->expr()->exists( - 'SELECT 1 FROM ' . AsideActivity::class . ' aside_activity_user_job_filter_act - JOIN aside_activity_user_job_filter_act.agent aside_activity_user_job_filter_user WHERE aside_activity_user_job_filter_user.userJob IN (:aside_activity_user_job_filter_jobs) AND aside_activity_user_job_filter_act = aside' + 'SELECT 1 FROM '.AsideActivity::class." {$p}_act " + ."JOIN {$p}_act.agent {$p}_user " + .'JOIN '.UserJobHistory::class." {$p}_history WITH {$p}_history.user = {$p}_user " + ."WHERE {$p}_act = aside " + // job_at based on aside.date + ."AND {$p}_history.startDate <= aside.date " + ."AND ({$p}_history.endDate IS NULL OR {$p}_history.endDate > aside.date) " + ."AND {$p}_history.job IN ( :{$p}_jobs )" ) ) - ->setParameter('aside_activity_user_job_filter_jobs', $data['jobs']); + ->setParameter( + "{$p}_jobs", + $data['jobs'], + ); } - public function applyOn() + public function applyOn(): string { return Declarations::ASIDE_ACTIVITY_TYPE; } public function buildForm(FormBuilderInterface $builder) { - $builder->add('jobs', EntityType::class, [ - 'class' => UserJob::class, - 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('jobs', EntityType::class, [ + 'class' => UserJob::class, + 'choices' => $this->userJobRepository->findAllActive(), + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), + 'multiple' => true, + 'expanded' => true, + ]); } - public function describeAction($data, $format = 'string') + public function describeAction($data, $format = 'string'): array { - return ['export.filter.Filtered aside activities by user jobs: only %jobs%', [ + return ['export.filter.by_user_job.Filtered aside activities by user jobs: only %jobs%', [ '%jobs%' => implode( ', ', array_map( fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), - $data['jobs']->toArray() + $data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs'] ) ), ]]; } - public function getTitle() + public function getFormDefaultData(): array { - return 'export.filter.Filter by user jobs'; + return [ + 'jobs' => [], + ]; + } + + public function getTitle(): string + { + return 'export.filter.by_user_job.Filter by user jobs'; } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php index 8fa51866d..fd0511e33 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php @@ -14,25 +14,23 @@ namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; class ByUserScopeFilter implements FilterInterface { - private ScopeRepositoryInterface $scopeRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; + private const PREFIX = 'aside_act_filter_user_scope'; public function __construct( - ScopeRepositoryInterface $scopeRepository, - TranslatableStringHelperInterface $translatableStringHelper + private readonly ScopeRepositoryInterface $scopeRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper ) { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -42,51 +40,66 @@ class ByUserScopeFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { + $p = self::PREFIX; + $qb ->andWhere( $qb->expr()->exists( - 'SELECT 1 FROM ' . AsideActivity::class . ' aside_activity_user_scope_filter_act - JOIN aside_activity_user_scope_filter_act.agent aside_activity_user_scope_filter_user WHERE aside_activity_user_scope_filter_user.mainScope IN (:aside_activity_user_scope_filter_scopes) AND aside_activity_user_scope_filter_act = aside ' + 'SELECT 1 FROM '.AsideActivity::class." {$p}_act " + ."JOIN {$p}_act.agent {$p}_user " + .'JOIN '.UserScopeHistory::class." {$p}_history WITH {$p}_history.user = {$p}_user " + ."WHERE {$p}_act = aside " + // scope_at based on aside.date + ."AND {$p}_history.startDate <= aside.date " + ."AND ({$p}_history.endDate IS NULL OR {$p}_history.endDate > aside.date) " + ."AND {$p}_history.scope IN ( :{$p}_scopes )" ) ) - ->setParameter('aside_activity_user_scope_filter_scopes', $data['scopes']); + ->setParameter( + "{$p}_scopes", + $data['scopes'], + ); } - public function applyOn() + public function applyOn(): string { return Declarations::ASIDE_ACTIVITY_TYPE; } public function buildForm(FormBuilderInterface $builder) { - $builder->add('scopes', EntityType::class, [ - 'class' => Scope::class, - 'choices' => $this->scopeRepository->findAllActive(), - 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('scopes', EntityType::class, [ + 'class' => Scope::class, + 'choices' => $this->scopeRepository->findAllActive(), + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + 'multiple' => true, + 'expanded' => true, + ]); } public function describeAction($data, $format = 'string') { - return ['export.filter.Filtered aside activities by user scope: only %scopes%', [ + return ['export.filter.by_user_scope.Filtered aside activities by user scope: only %scopes%', [ '%scopes%' => implode( ', ', array_map( fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), - $data['scopes']->toArray() + $data['scopes'] instanceof Collection ? $data['scopes']->toArray() : $data['scopes'] ) ), ]]; } - public function getTitle() + public function getFormDefaultData(): array { - return 'export.filter.Filter by user scope'; + return [ + 'scopes' => [], + ]; + } + + public function getTitle(): string + { + return 'export.filter.by_user_scope.Filter by user scope'; } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php index c183fcea6..c285f0f9b 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php @@ -22,12 +22,8 @@ use Symfony\Component\Form\FormBuilderInterface; final class AsideActivityCategoryType extends AbstractType { - private CategoryRender $categoryRender; - - public function __construct( - CategoryRender $categoryRender - ) { - $this->categoryRender = $categoryRender; + public function __construct(private readonly CategoryRender $categoryRender) + { } public function buildForm(FormBuilderInterface $builder, array $options) diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php index 727287972..d6fcb821c 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php @@ -16,10 +16,7 @@ use Chill\AsideActivityBundle\Form\Type\PickAsideActivityCategoryType; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\PickUserDynamicType; -use DateInterval; -use DateTime; -use DateTimeImmutable; -use DateTimeZone; +use Chill\MainBundle\Form\Type\PickUserLocationType; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; @@ -29,11 +26,9 @@ use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; -use function in_array; - final class AsideActivityFormType extends AbstractType { - private array $timeChoices; + private readonly array $timeChoices; public function __construct( ParameterBagInterface $parameterBag, @@ -65,7 +60,7 @@ final class AsideActivityFormType extends AbstractType ChillDateType::class, [ 'label' => 'date', - 'data' => new DateTime(), + 'data' => new \DateTime(), 'required' => true, ] ) @@ -77,7 +72,9 @@ final class AsideActivityFormType extends AbstractType ->add('note', ChillTextareaType::class, [ 'label' => 'Note', 'required' => false, - ]); + ]) + ->add('location', PickUserLocationType::class) + ; foreach (['duration'] as $fieldName) { $builder->get($fieldName) @@ -93,25 +90,19 @@ final class AsideActivityFormType extends AbstractType ) { // set the timezone to GMT, and fix the difference between current and GMT // the datetimetransformer will then handle timezone as GMT - $timezoneUTC = new DateTimeZone('GMT'); - /** @var DateTimeImmutable $data */ - $data = $formEvent->getData() ?? DateTime::createFromFormat('U', '300'); + $timezoneUTC = new \DateTimeZone('GMT'); + /** @var \DateTimeImmutable $data */ + $data = $formEvent->getData() ?? \DateTime::createFromFormat('U', '300'); $seconds = $data->getTimezone()->getOffset($data); $data->setTimeZone($timezoneUTC); - $data->add(new DateInterval('PT' . $seconds . 'S')); + $data->add(new \DateInterval('PT'.$seconds.'S')); // test if the timestamp is in the choices. // If not, recreate the field with the new timestamp - if (!in_array($data->getTimestamp(), $timeChoices, true)) { + if (!\in_array($data->getTimestamp(), $timeChoices, true)) { // the data are not in the possible values. add them $timeChoices[$data->format('H:i')] = $data->getTimestamp(); - $form = $builder->create($fieldName, ChoiceType::class, array_merge( - $durationTimeOptions, - [ - 'choices' => $timeChoices, - 'auto_initialize' => false, - ] - )); + $form = $builder->create($fieldName, ChoiceType::class, [...$durationTimeOptions, 'choices' => $timeChoices, 'auto_initialize' => false]); $form->addModelTransformer($durationTimeTransformer); $formEvent->getForm()->getParent()->add($form->getForm()); } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php index 3ee392517..8341d8595 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php @@ -20,12 +20,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver; final class PickAsideActivityCategoryType extends AbstractType { - private CategoryRender $categoryRender; - - public function __construct( - CategoryRender $categoryRender - ) { - $this->categoryRender = $categoryRender; + public function __construct(private readonly CategoryRender $categoryRender) + { } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php index 8cc076a6e..d0a593dab 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php @@ -14,13 +14,10 @@ namespace Chill\AsideActivityBundle\Menu; use Knp\Menu\MenuItem; use Symfony\Component\Security\Core\Security; -final class AdminMenuBuilder implements \Chill\MainBundle\Routing\LocalMenuBuilderInterface +final readonly class AdminMenuBuilder implements \Chill\MainBundle\Routing\LocalMenuBuilderInterface { - private Security $security; - - public function __construct(Security $security) + public function __construct(private Security $security) { - $this->security = $security; } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php index f23fcdc8a..0646a8613 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php @@ -21,19 +21,10 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class SectionMenuBuilder implements LocalMenuBuilderInterface { - public AuthorizationCheckerInterface $authorizationChecker; - - protected TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator, AuthorizationCheckerInterface $authorizationChecker) + public function __construct(protected TranslatorInterface $translator, public AuthorizationCheckerInterface $authorizationChecker) { - $this->translator = $translator; - $this->authorizationChecker = $authorizationChecker; } - /** - * @param $menuId - */ public function buildMenu($menuId, MenuItem $menu, array $parameters) { if ($this->authorizationChecker->isGranted('ROLE_USER')) { diff --git a/src/Bundle/ChillAsideActivityBundle/src/Repository/AsideActivityCategoryRepository.php b/src/Bundle/ChillAsideActivityBundle/src/Repository/AsideActivityCategoryRepository.php index 918cec586..6735606ce 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Repository/AsideActivityCategoryRepository.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Repository/AsideActivityCategoryRepository.php @@ -18,7 +18,7 @@ use Doctrine\Persistence\ObjectRepository; class AsideActivityCategoryRepository implements ObjectRepository { - private EntityRepository $repository; + private readonly EntityRepository $repository; public function __construct(EntityManagerInterface $entityManager) { diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig index 5ffc73684..1e2711bfe 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig @@ -39,6 +39,9 @@ {% endif %}
      + {%- if entity.location.name is defined -%} +
      {{ entity.location.name }}
      + {%- endif -%}
      diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig index 75da9a444..8fb487d31 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig @@ -22,6 +22,13 @@
      {{ 'Created for'|trans }}
      {{ entity.agent }}
      + +
      {{ 'Asideactivity location'|trans }}
      + {%- if entity.location.name is defined -%} +
      {{ entity.location.name }}
      + {%- else -%} +
      {{ 'No data given'|trans }}
      + {%- endif -%}

      {{ 'Activity data'|trans }}

      diff --git a/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php b/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php index 6b308b5f2..e15abd439 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php @@ -21,9 +21,9 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class AsideActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface { - public const STATS = 'CHILL_ASIDE_ACTIVITY_STATS'; + final public const STATS = 'CHILL_ASIDE_ACTIVITY_STATS'; - private VoterHelperInterface $voterHelper; + private readonly VoterHelperInterface $voterHelper; public function __construct( VoterHelperFactoryInterface $voterHelperFactory diff --git a/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php b/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php index 066a0ca52..db8995087 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php @@ -14,12 +14,11 @@ namespace Chill\AsideActivityBundle\Templating\Entity; use Chill\AsideActivityBundle\Entity\AsideActivityCategory; use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Component\Templating\EngineInterface; /** * @implements ChillEntityRenderInterface */ -final class CategoryRender implements ChillEntityRenderInterface +final readonly class CategoryRender implements ChillEntityRenderInterface { public const DEFAULT_ARGS = [ self::SEPERATOR_KEY => ' > ', @@ -27,14 +26,8 @@ final class CategoryRender implements ChillEntityRenderInterface public const SEPERATOR_KEY = 'default.separator'; - private EngineInterface $engine; - - private TranslatableStringHelper $translatableStringHelper; - - public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine) + public function __construct(private TranslatableStringHelper $translatableStringHelper, private \Twig\Environment $engine) { - $this->translatableStringHelper = $translatableStringHelper; - $this->engine = $engine; } public function buildParents(AsideActivityCategory $asideActivityCategory) diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Chill/DocStoreBundle/Tests/Security/Guard/DavTokenAuthenticationEventSubscriberTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Chill/DocStoreBundle/Tests/Security/Guard/DavTokenAuthenticationEventSubscriberTest.php new file mode 100644 index 000000000..875e40a65 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Chill/DocStoreBundle/Tests/Security/Guard/DavTokenAuthenticationEventSubscriberTest.php @@ -0,0 +1,66 @@ + 1, + 'so' => '1234', + 'e' => 1, + ], $token); + + $eventSubscriber->onJWTAuthenticated($event); + + self::assertTrue($token->hasAttribute(DavTokenAuthenticationEventSubscriber::STORED_OBJECT)); + self::assertTrue($token->hasAttribute(DavTokenAuthenticationEventSubscriber::ACTIONS)); + self::assertEquals('1234', $token->getAttribute(DavTokenAuthenticationEventSubscriber::STORED_OBJECT)); + self::assertEquals(StoredObjectRoleEnum::EDIT, $token->getAttribute(DavTokenAuthenticationEventSubscriber::ACTIONS)); + } + + public function testOnJWTAuthenticatedWithDavNoDataInPayload(): void + { + $eventSubscriber = new DavTokenAuthenticationEventSubscriber(); + $token = new class () extends AbstractToken { + public function getCredentials() + { + return null; + } + }; + $event = new JWTAuthenticatedEvent([], $token); + + $eventSubscriber->onJWTAuthenticated($event); + + self::assertFalse($token->hasAttribute(DavTokenAuthenticationEventSubscriber::STORED_OBJECT)); + self::assertFalse($token->hasAttribute(DavTokenAuthenticationEventSubscriber::ACTIONS)); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php index 196dd5bc3..4a772775f 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php @@ -15,25 +15,22 @@ use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\MainBundle\Test\PrepareClientTrait; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -use function array_pop; -use function shuffle; /** * @internal + * * @coversNothing */ final class AsideActivityControllerTest extends WebTestCase { use PrepareClientTrait; - protected function setUp(): void + protected function tearDown(): void { - parent::setUp(); - self::bootKernel(); - $this->client = $this->getClientAuthenticated(); + self::ensureKernelShutdown(); } - public function generateAsideActivityId() + public function generateAsideActivityId(): iterable { self::bootKernel(); @@ -50,13 +47,15 @@ final class AsideActivityControllerTest extends WebTestCase ->getQuery() ->getResult(); - shuffle($asideActivityIds); + \shuffle($asideActivityIds); - yield [array_pop($asideActivityIds)['id']]; + yield [\array_pop($asideActivityIds)['id']]; - yield [array_pop($asideActivityIds)['id']]; + yield [\array_pop($asideActivityIds)['id']]; - yield [array_pop($asideActivityIds)['id']]; + yield [\array_pop($asideActivityIds)['id']]; + + self::ensureKernelShutdown(); } /** @@ -64,22 +63,28 @@ final class AsideActivityControllerTest extends WebTestCase */ public function testEditWithoutUsers(int $asideActivityId) { - $this->client->request('GET', "/fr/asideactivity/{$asideActivityId}/edit"); + self::ensureKernelShutdown(); + $client = $this->getClientAuthenticated(); + $client->request('GET', "/fr/asideactivity/{$asideActivityId}/edit"); - $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + $this->assertEquals(200, $client->getResponse()->getStatusCode()); } public function testIndexWithoutUsers() { - $this->client->request('GET', '/fr/asideactivity'); + self::ensureKernelShutdown(); + $client = $this->getClientAuthenticated(); + $client->request('GET', '/fr/asideactivity'); - $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + $this->assertEquals(200, $client->getResponse()->getStatusCode()); } public function testNewWithoutUsers() { - $this->client->request('GET', '/fr/asideactivity/new'); + self::ensureKernelShutdown(); + $client = $this->getClientAuthenticated(); + $client->request('GET', '/fr/asideactivity/new'); - $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + $this->assertEquals(200, $client->getResponse()->getStatusCode()); } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php new file mode 100644 index 000000000..29ea8d1f6 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php @@ -0,0 +1,43 @@ +listAsideActivity = self::$container->get(ListAsideActivity::class); + } + + public function testExecuteQuery(): void + { + $qb = $this->listAsideActivity->initiateQuery([], [], []) + ->setMaxResults(1); + + $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); + + self::assertIsArray($results, 'smoke test: test that the result is an array'); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/routes.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/routes.yaml index ddb635084..0a193b990 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/routes.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/routes.yaml @@ -1,12 +1,3 @@ chill_asideactivities_controllers: resource: "@ChillAsideActivityBundle/Controller" type: annotation - -chill_admin_aside_activity_redirect_to_admin_index: - path: /{_locale}/admin/activity_redirect_to_main - controller: Chill\ActivityBundle\Controller\AdminController::redirectToAdminIndexAction - options: - menus: - admin_aside_activity: - order: 0 - label: Main admin menu diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml index a29413e15..40cb120da 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml @@ -46,19 +46,27 @@ services: tags: - { name: chill.export_filter, alias: 'aside_activity_user_filter' } + Chill\AsideActivityBundle\Export\Filter\ByLocationFilter: + tags: + - { name: chill.export_filter, alias: 'aside_activity_location_filter' } + ## Aggregators chill.aside_activity.export.type_aggregator: class: Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator tags: - - { name: chill.export_aggregator, alias: activity_type_aggregator } + - { name: chill.export_aggregator, alias: 'activity_type_aggregator' } chill.aside_activity.export.user_job_aggregator: class: Chill\AsideActivityBundle\Export\Aggregator\ByUserJobAggregator tags: - - { name: chill.export_aggregator, alias: aside_activity_user_job_aggregator } + - { name: chill.export_aggregator, alias: 'aside_activity_user_job_aggregator' } chill.aside_activity.export.user_scope_aggregator: class: Chill\AsideActivityBundle\Export\Aggregator\ByUserScopeAggregator tags: - - { name: chill.export_aggregator, alias: aside_activity_user_scope_aggregator } + - { name: chill.export_aggregator, alias: 'aside_activity_user_scope_aggregator' } + + Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator: + tags: + - { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' } diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20230816112809.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20230816112809.php new file mode 100644 index 000000000..bde8af1e3 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20230816112809.php @@ -0,0 +1,39 @@ +addSql('DROP INDEX chill_asideactivity.IDX_A866DA0E64D218E'); + $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP CONSTRAINT FK_A866DA0E64D218E'); + $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP location_id'); + $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity ADD location VARCHAR(100) DEFAULT NULL'); + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD location_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_asideactivity.asideactivity DROP location'); + $this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD CONSTRAINT FK_A866DA0E64D218E FOREIGN KEY (location_id) REFERENCES chill_main_location (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_A866DA0E64D218E ON chill_asideactivity.asideactivity (location_id)'); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml index 25d07bd22..7d3c7a20e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml +++ b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml @@ -26,6 +26,7 @@ Users: Utilisateurs Emergency: Urgent by: "Par " location: Lieu +Asideactivity location: Localisation de l'activité # Crud crud: @@ -71,21 +72,21 @@ days: jours 1 hour 30: 1 heure 30 1 hour 45: 1 heure 45 2 hours: 2 heures -2 hours 30: 2 heure 30 +2 hours 30: 2 heures 30 3 hours: 3 heures -3 hours 30: 3 heure 30 +3 hours 30: 3 heures 30 4 hours: 4 heures -4 hours 30: 4 heure 30 +4 hours 30: 4 heures 30 5 hours: 5 heures -5 hours 30: 5 heure 30 +5 hours 30: 5 heures 30 6 hours: 6 heures -6 hours 30: 6 heure 30 +6 hours 30: 6 heures 30 7 hours: 7 heures -7 hours 30: 7 heure 30 +7 hours 30: 7 heures 30 8 hours: 8 heures -8 hours 30: 8 heure 30 +8 hours 30: 8 heures 30 9 hours: 9 heures -9 hours 30: 9 heure 30 +9 hours 30: 9 heures 30 10 hours: 10 heures 1/2 day: 1/2 jour 1 day: 1 jour @@ -182,6 +183,7 @@ export: duration: Durée note: Note id: Identifiant + location: Localisation Exports of aside activities: Exports des activités annexes Count aside activities: Nombre d'activités annexes @@ -198,15 +200,24 @@ export: Aside activities before this date: Actvitités annexes avant cette date 'Filtered aside activity by user: only %users%': "Filtré par utilisateur: uniquement %users%" Filter aside activity by user: Filtrer par utilisateur - 'Filtered aside activities by user jobs: only %jobs%': "Filtré par métier des utilisateurs: uniquement %jobs%" - Filter by user jobs: Filtrer les activités annexes par métier des utilisateurs - 'Filtered aside activities by user scope: only %scopes%': "Filtré par service des utilisateur: uniquement %scopes%" - Filter by user scope: Filtrer les activités annexes par service d'utilisateur + by_user_job: + 'Filtered aside activities by user jobs: only %jobs%': "Filtré par métier des utilisateurs: uniquement %jobs%" + Filter by user jobs: Filtrer les activités annexes par métier des utilisateurs + by_user_scope: + 'Filtered aside activities by user scope: only %scopes%': "Filtré par service des utilisateurs: uniquement %scopes%" + Filter by user scope: Filtrer les activités annexes par service d'utilisateur + Filter by aside activity location: Filtrer les activités annexes par localisation + 'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%" aggregator: Group by aside activity type: Grouper les activités annexes par type d'activité Aside activity type: Type d'activité annexe - Aggregate by user job: Grouper les activités annexes par métier des utilisateurs - Aggregate by user scope: Grouper les activités annexes par service des utilisateurs + by_user_job: + Aggregate by user job: Grouper les activités annexes par métier des utilisateurs + by_user_scope: + Aggregate by user scope: Grouper les activités annexes par service des utilisateurs + Aside activity location: Localisation des activités annexe + Group by aside activity location: Grouper les activités annexes par localisation + Aside activity localisation: Localisation # ROLES CHILL_ASIDE_ACTIVITY_STATS: Statistiques pour les activités annexes diff --git a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorInterface.php b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorInterface.php index 2662b896a..3b5a687a6 100644 --- a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorInterface.php +++ b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorInterface.php @@ -17,7 +17,7 @@ use Chill\BudgetBundle\Entity\Resource; interface CalculatorInterface { /** - * @param array $elements + * @param array $elements */ public function calculate(array $elements): ?CalculatorResult; diff --git a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php index 7987bdfdf..477f23189 100644 --- a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php +++ b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php @@ -11,14 +11,8 @@ declare(strict_types=1); namespace Chill\BudgetBundle\Calculator; -use Chill\BudgetBundle\Entity\AbstractElement; use Chill\BudgetBundle\Entity\Charge; use Chill\BudgetBundle\Entity\Resource; -use OutOfBoundsException; - -use function array_key_exists; -use function array_keys; -use function implode; class CalculatorManager { @@ -42,7 +36,7 @@ class CalculatorManager } /** - * @param array $elements + * @param array $elements * * @return CalculatorResult[] */ @@ -63,9 +57,8 @@ class CalculatorManager public function getCalculator(string $alias): CalculatorInterface { - if (false === array_key_exists($alias, $this->calculators)) { - throw new OutOfBoundsException("The calculator with alias '{$alias}' does " - . 'not exists. Possible values are ' . implode(', ', array_keys($this->calculators))); + if (false === \array_key_exists($alias, $this->calculators)) { + throw new \OutOfBoundsException("The calculator with alias '{$alias}' does ".'not exists. Possible values are '.\implode(', ', \array_keys($this->calculators))); } return $this->calculators[$alias]; diff --git a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php index 2a67486f0..ce31dcaaf 100644 --- a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php +++ b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php @@ -13,11 +13,11 @@ namespace Chill\BudgetBundle\Calculator; class CalculatorResult { - public const TYPE_CURRENCY = 'currency'; + final public const TYPE_CURRENCY = 'currency'; - public const TYPE_PERCENTAGE = 'percentage'; + final public const TYPE_PERCENTAGE = 'percentage'; - public const TYPE_RATE = 'rate'; + final public const TYPE_RATE = 'rate'; public $label; diff --git a/src/Bundle/ChillBudgetBundle/ChillBudgetBundle.php b/src/Bundle/ChillBudgetBundle/ChillBudgetBundle.php index 30bf1e68a..71f6a7076 100644 --- a/src/Bundle/ChillBudgetBundle/ChillBudgetBundle.php +++ b/src/Bundle/ChillBudgetBundle/ChillBudgetBundle.php @@ -20,6 +20,6 @@ class ChillBudgetBundle extends Bundle { parent::build($container); - $container->addCompilerPass(new CalculatorCompilerPass()); + $container->addCompilerPass(new CalculatorCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); } } diff --git a/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php b/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php index 125c72ce4..5282d6bce 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php @@ -23,24 +23,10 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\Translation\TranslatorInterface; -use function get_class; - abstract class AbstractElementController extends AbstractController { - protected LoggerInterface $chillMainLogger; - - protected EntityManagerInterface $em; - - protected TranslatorInterface $translator; - - public function __construct( - EntityManagerInterface $em, - TranslatorInterface $translator, - LoggerInterface $chillMainLogger - ) { - $this->em = $em; - $this->translator = $translator; - $this->chillMainLogger = $chillMainLogger; + public function __construct(protected EntityManagerInterface $em, protected TranslatorInterface $translator, protected LoggerInterface $chillMainLogger) + { } /** @@ -48,14 +34,11 @@ abstract class AbstractElementController extends AbstractController * "{_locale}/family-members/family-members/{id}/delete", * name="chill_family_members_family_members_delete" * ). - * - * @param mixed $template - * @param mixed $flashMessage */ - protected function _delete(AbstractElement $element, Request $request, $template, $flashMessage): Response + protected function _delete(AbstractElement $element, Request $request, mixed $template, mixed $flashMessage): Response { $this->denyAccessUnlessGranted(BudgetElementVoter::DELETE, $element, 'You are not ' - . 'allowed to delete this item'); + .'allowed to delete this item'); $form = $this->createDeleteForm(); @@ -67,12 +50,12 @@ abstract class AbstractElementController extends AbstractController $indexPage = 'chill_budget_elements_household_index'; } - if ($request->getMethod() === Request::METHOD_DELETE) { + if (Request::METHOD_DELETE === $request->getMethod()) { $form->handleRequest($request); - if ($form->isValid()) { + if ($form->isSubmitted() && $form->isValid()) { $this->chillMainLogger->notice('A budget element has been removed', [ - 'family_element' => get_class($element), + 'family_element' => $element::class, 'by_user' => $this->getUser()->getUsername(), 'family_member_id' => $element->getId(), 'amount' => $element->getAmount(), @@ -141,12 +124,7 @@ abstract class AbstractElementController extends AbstractController ]); } - /** - * @param mixed $template - * @param mixed $flashMessageOnSuccess - * @param mixed $entity - */ - protected function _new($entity, Request $request, $template, $flashMessageOnSuccess) + protected function _new(mixed $entity, Request $request, mixed $template, mixed $flashMessageOnSuccess) { /** @var AbstractElement $element */ $element = $this->createNewElement(); @@ -196,10 +174,8 @@ abstract class AbstractElementController extends AbstractController * "{_locale}/family-members/family-members/{id}/view", * name="chill_family_members_family_members_view" * ). - * - * @param mixed $template */ - protected function _view(AbstractElement $element, $template) + protected function _view(AbstractElement $element, mixed $template) { $this->denyAccessUnlessGranted(BudgetElementVoter::SEE, $element); diff --git a/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php b/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php index e1989176d..a84d54e40 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php @@ -20,9 +20,9 @@ class ChargeKindController extends CRUDController { protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { - /** @var QueryBuilder $query */ + /* @var QueryBuilder $query */ $query->addOrderBy('e.ordering', 'ASC'); - return $query; + return parent::orderQuery($action, $query, $request, $paginator); } } diff --git a/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php b/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php index 90b8e750a..ffef467f9 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php @@ -20,9 +20,9 @@ class ResourceKindController extends CRUDController { protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { - /** @var QueryBuilder $query */ + /* @var QueryBuilder $query */ $query->addOrderBy('e.ordering', 'ASC'); - return $query; + return parent::orderQuery($action, $query, $request, $paginator); } } diff --git a/src/Bundle/ChillBudgetBundle/Controller/ChargeController.php b/src/Bundle/ChillBudgetBundle/Controller/ChargeController.php index 5d4a8b3a4..39e96d4a8 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/ChargeController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/ChargeController.php @@ -15,18 +15,14 @@ use Chill\BudgetBundle\Entity\Charge; use Chill\BudgetBundle\Form\ChargeType; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; class ChargeController extends AbstractElementController { /** - * @Route( - * "{_locale}/budget/charge/{id}/delete", - * name="chill_budget_charge_delete" - * ) - * * @return \Symfony\Component\HttpFoundation\Response + * + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/charge/{id}/delete", name="chill_budget_charge_delete") */ public function deleteAction(Request $request, Charge $charge) { @@ -39,12 +35,9 @@ class ChargeController extends AbstractElementController } /** - * @Route( - * "{_locale}/budget/charge/{id}/edit", - * name="chill_budget_charge_edit" - * ) - * * @return \Symfony\Component\HttpFoundation\Response + * + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/charge/{id}/edit", name="chill_budget_charge_edit") */ public function editAction(Request $request, Charge $charge) { @@ -57,12 +50,9 @@ class ChargeController extends AbstractElementController } /** - * @Route( - * "{_locale}/budget/charge/by-person/{id}/new", - * name="chill_budget_charge_new" - * ) - * * @return \Symfony\Component\HttpFoundation\Response + * + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/charge/by-person/{id}/new", name="chill_budget_charge_new") */ public function newAction(Request $request, Person $person) { @@ -75,12 +65,9 @@ class ChargeController extends AbstractElementController } /** - * @Route( - * "{_locale}/budget/charge/by-household/{id}/new", - * name="chill_budget_charge_household_new" - * ) - * * @return \Symfony\Component\HttpFoundation\Response + * + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/charge/by-household/{id}/new", name="chill_budget_charge_household_new") */ public function newHouseholdAction(Request $request, Household $household) { @@ -93,12 +80,9 @@ class ChargeController extends AbstractElementController } /** - * @Route( - * "{_locale}/budget/charge/{id}/view", - * name="chill_budget_charge_view" - * ) - * * @return \Symfony\Component\HttpFoundation\Response + * + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/charge/{id}/view", name="chill_budget_charge_view") */ public function viewAction(Charge $charge) { diff --git a/src/Bundle/ChillBudgetBundle/Controller/ElementController.php b/src/Bundle/ChillBudgetBundle/Controller/ElementController.php index b5db3cd30..468576673 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/ElementController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/ElementController.php @@ -12,46 +12,21 @@ declare(strict_types=1); namespace Chill\BudgetBundle\Controller; use Chill\BudgetBundle\Calculator\CalculatorManager; -use Chill\BudgetBundle\Entity\Charge; -use Chill\BudgetBundle\Entity\Resource; use Chill\BudgetBundle\Repository\ChargeRepository; use Chill\BudgetBundle\Repository\ResourceRepository; use Chill\BudgetBundle\Security\Authorization\BudgetElementVoter; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; -use DateTime; -use Doctrine\ORM\EntityManagerInterface; -use Psr\Log\LoggerInterface; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Contracts\Translation\TranslatorInterface; - -use function array_merge; -use function count; class ElementController extends AbstractController { - private CalculatorManager $calculator; - - private ResourceRepository $resourceRepository; - - private ChargeRepository $chargeRepository; - - public function __construct( - CalculatorManager $calculator, - ResourceRepository $resourceRepository, - ChargeRepository $chargeRepository, - ) { - $this->calculator = $calculator; - $this->resourceRepository = $resourceRepository; - $this->chargeRepository = $chargeRepository; + public function __construct(private readonly CalculatorManager $calculator, private readonly ResourceRepository $resourceRepository, private readonly ChargeRepository $chargeRepository) + { } /** - * @Route( - * "{_locale}/budget/elements/by-person/{id}", - * name="chill_budget_elements_index" - * ) + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/elements/by-person/{id}", name="chill_budget_elements_index") */ public function indexAction(Person $person) { @@ -60,13 +35,13 @@ class ElementController extends AbstractController $charges = $this->chargeRepository->findAllByEntity($person); $resources = $this->resourceRepository->findAllByEntity($person); - $elements = array_merge($charges, $resources); + $elements = \array_merge($charges, $resources); - if (count($elements) > 0) { + if (\count($elements) > 0) { $results = $this->calculator->calculateDefault($elements); } - return $this->render('ChillBudgetBundle:Person:index.html.twig', [ + return $this->render('@ChillBudget/Person/index.html.twig', [ 'person' => $person, 'charges' => $charges, 'resources' => $resources, @@ -75,10 +50,7 @@ class ElementController extends AbstractController } /** - * @Route( - * "{_locale}/budget/elements/by-household/{id}", - * name="chill_budget_elements_household_index" - * ) + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/elements/by-household/{id}", name="chill_budget_elements_household_index") */ public function indexHouseholdAction(Household $household) { @@ -87,13 +59,13 @@ class ElementController extends AbstractController $charges = $this->chargeRepository->findAllByEntity($household); $resources = $this->resourceRepository->findAllByEntity($household); - $elements = array_merge($charges, $resources); + $elements = \array_merge($charges, $resources); - if (count($elements) > 0) { + if (\count($elements) > 0) { $results = $this->calculator->calculateDefault($elements); } - return $this->render('ChillBudgetBundle:Household:index.html.twig', [ + return $this->render('@ChillBudget/Household/index.html.twig', [ 'household' => $household, 'charges' => $charges, 'resources' => $resources, diff --git a/src/Bundle/ChillBudgetBundle/Controller/ResourceController.php b/src/Bundle/ChillBudgetBundle/Controller/ResourceController.php index e7f1e9ff3..f67d92a43 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/ResourceController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/ResourceController.php @@ -15,17 +15,13 @@ use Chill\BudgetBundle\Entity\Resource; use Chill\BudgetBundle\Form\ResourceType; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class ResourceController extends AbstractElementController { /** - * @Route( - * "{_locale}/budget/resource/{id}/delete", - * name="chill_budget_resource_delete" - * ) + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/resource/{id}/delete", name="chill_budget_resource_delete") */ public function deleteAction(Request $request, Resource $resource) { @@ -38,10 +34,7 @@ class ResourceController extends AbstractElementController } /** - * @Route( - * "{_locale}/budget/resource/{id}/edit", - * name="chill_budget_resource_edit" - * ) + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/resource/{id}/edit", name="chill_budget_resource_edit") */ public function editAction(Request $request, Resource $resource): Response { @@ -56,10 +49,7 @@ class ResourceController extends AbstractElementController /** * Create a new budget element for a person. * - * @Route( - * "{_locale}/budget/resource/by-person/{id}/new", - * name="chill_budget_resource_new" - * ) + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/resource/by-person/{id}/new", name="chill_budget_resource_new") */ public function newAction(Request $request, Person $person): Response { @@ -74,10 +64,7 @@ class ResourceController extends AbstractElementController /** * Create new budget element for a household. * - * @Route( - * "{_locale}/budget/resource/by-household/{id}/new", - * name="chill_budget_resource_household_new" - * ) + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/resource/by-household/{id}/new", name="chill_budget_resource_household_new") */ public function newHouseholdAction(Request $request, Household $household): Response { @@ -90,10 +77,7 @@ class ResourceController extends AbstractElementController } /** - * @Route( - * "{_locale}/budget/resource/{id}/view", - * name="chill_budget_resource_view" - * ) + * @\Symfony\Component\Routing\Annotation\Route("{_locale}/budget/resource/{id}/view", name="chill_budget_resource_view") */ public function viewAction(Resource $resource): Response { diff --git a/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php b/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php index 57976f846..6a8842b64 100644 --- a/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php +++ b/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php @@ -34,7 +34,7 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services/form.yaml'); $loader->load('services/repository.yaml'); $loader->load('services/security.yaml'); @@ -60,7 +60,7 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac */ public function prependRoutes(ContainerBuilder $container) { - //add routes for custom bundle + // add routes for custom bundle $container->prependExtensionConfig('chill_main', [ 'routing' => [ 'resources' => [ diff --git a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php index 619a6cdd7..85420e670 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php +++ b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php @@ -14,8 +14,6 @@ namespace Chill\BudgetBundle\Entity; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; use DateTime; -use DateTimeImmutable; -use DateTimeInterface; use Doctrine\ORM\Mapping as ORM; use Ramsey\Uuid\Type\Decimal; use Symfony\Component\Validator\Constraints as Assert; @@ -29,9 +27,11 @@ abstract class AbstractElement { /** * @ORM\Column(name="amount", type="decimal", precision=10, scale=2) + * * @Assert\GreaterThan( * value=0 * ) + * * @Assert\NotNull( * message="The amount cannot be empty" * ) @@ -45,12 +45,13 @@ abstract class AbstractElement /** * @ORM\Column(name="endDate", type="datetime_immutable", nullable=true) + * * @Assert\GreaterThan( * propertyPath="startDate", * message="The budget element's end date must be after the start date" * ) */ - private ?DateTimeImmutable $endDate = null; + private ?\DateTimeImmutable $endDate = null; /** * @ORM\ManyToOne( @@ -68,16 +69,17 @@ abstract class AbstractElement /** * @ORM\Column(name="startDate", type="datetime_immutable") + * * @Assert\Date */ - private DateTimeImmutable $startDate; + private \DateTimeImmutable $startDate; /** * @ORM\Column(name="type", type="string", length=255) */ private string $type = ''; - /*Getters and Setters */ + /* Getters and Setters */ public function getAmount(): float { @@ -89,7 +91,7 @@ abstract class AbstractElement return $this->comment; } - public function getEndDate(): ?DateTimeImmutable + public function getEndDate(): ?\DateTimeImmutable { return $this->endDate; } @@ -104,7 +106,7 @@ abstract class AbstractElement return $this->person; } - public function getStartDate(): DateTimeImmutable + public function getStartDate(): \DateTimeImmutable { return $this->startDate; } @@ -137,10 +139,10 @@ abstract class AbstractElement return $this; } - public function setEndDate(?DateTimeInterface $endDate = null): self + public function setEndDate(?\DateTimeInterface $endDate = null): self { - if ($endDate instanceof DateTime) { - $this->endDate = DateTimeImmutable::createFromMutable($endDate); + if ($endDate instanceof \DateTime) { + $this->endDate = \DateTimeImmutable::createFromMutable($endDate); } elseif (null === $endDate) { $this->endDate = null; } else { @@ -164,10 +166,10 @@ abstract class AbstractElement return $this; } - public function setStartDate(DateTimeInterface $startDate): self + public function setStartDate(\DateTimeInterface $startDate): self { - if ($startDate instanceof DateTime) { - $this->startDate = DateTimeImmutable::createFromMutable($startDate); + if ($startDate instanceof \DateTime) { + $this->startDate = \DateTimeImmutable::createFromMutable($startDate); } elseif (null === $startDate) { $this->startDate = null; } else { diff --git a/src/Bundle/ChillBudgetBundle/Entity/Charge.php b/src/Bundle/ChillBudgetBundle/Entity/Charge.php index 056e4abff..d6af5cc1e 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/Charge.php +++ b/src/Bundle/ChillBudgetBundle/Entity/Charge.php @@ -13,26 +13,26 @@ namespace Chill\BudgetBundle\Entity; use Chill\MainBundle\Entity\HasCentersInterface; use Chill\PersonBundle\Entity\Person; -use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; /** * Charge. * * @ORM\Table(name="chill_budget.charge") + * * @ORM\Entity(repositoryClass="Chill\BudgetBundle\Repository\ChargeRepository") */ class Charge extends AbstractElement implements HasCentersInterface { - public const HELP_ASKED = 'running'; + final public const HELP_ASKED = 'running'; - public const HELP_NO = 'no'; + final public const HELP_NO = 'no'; - public const HELP_NOT_RELEVANT = 'not-relevant'; + final public const HELP_NOT_RELEVANT = 'not-relevant'; - public const HELP_YES = 'yes'; + final public const HELP_YES = 'yes'; - public const HELPS = [ + final public const HELPS = [ self::HELP_ASKED, self::HELP_NO, self::HELP_YES, @@ -41,26 +41,28 @@ class Charge extends AbstractElement implements HasCentersInterface /** * @ORM\ManyToOne(targetEntity=ChargeKind::class, inversedBy="AbstractElement") + * * @ORM\JoinColumn */ private ?ChargeKind $charge = null; /** - * @var string * @ORM\Column(name="help", type="string", nullable=true) */ - private $help = self::HELP_NOT_RELEVANT; + private ?string $help = self::HELP_NOT_RELEVANT; /** * @ORM\Column(name="id", type="integer") + * * @ORM\Id + * * @ORM\GeneratedValue(strategy="AUTO") */ private ?int $id = null; public function __construct() { - $this->setStartDate(new DateTimeImmutable('today')); + $this->setStartDate(new \DateTimeImmutable('today')); } public function getCenters(): array diff --git a/src/Bundle/ChillBudgetBundle/Entity/ChargeKind.php b/src/Bundle/ChillBudgetBundle/Entity/ChargeKind.php index b6ebcf347..2196a0e99 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/ChargeKind.php +++ b/src/Bundle/ChillBudgetBundle/Entity/ChargeKind.php @@ -21,14 +21,18 @@ use Symfony\Component\Validator\Constraints as Assert; * @ORM\Table(name="chill_budget.charge_type", * uniqueConstraints={@ORM\UniqueConstraint(name="charge_kind_unique_type_idx", fields={"kind"})} * ) + * * @ORM\Entity + * * @UniqueEntity(fields={"kind"}) */ class ChargeKind { /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") */ private ?int $id = null; @@ -40,7 +44,9 @@ class ChargeKind /** * @ORM\Column(type="string", length=255, options={"default": ""}, nullable=false) + * * @Assert\Regex(pattern="/^[a-z0-9\-_]{1,}$/", message="budget.admin.form.kind.only_alphanumeric") + * * @Assert\Length(min=3) */ private string $kind = ''; @@ -70,7 +76,7 @@ class ChargeKind return $this->isActive; } - public function getKind(): ?string + public function getKind(): string { return $this->kind; } @@ -92,7 +98,7 @@ class ChargeKind return $this; } - public function setKind(?string $kind): self + public function setKind(string $kind): self { $this->kind = $kind; diff --git a/src/Bundle/ChillBudgetBundle/Entity/Resource.php b/src/Bundle/ChillBudgetBundle/Entity/Resource.php index 28b8645e0..c4845ed1b 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/Resource.php +++ b/src/Bundle/ChillBudgetBundle/Entity/Resource.php @@ -13,33 +13,36 @@ namespace Chill\BudgetBundle\Entity; use Chill\MainBundle\Entity\HasCentersInterface; use Chill\PersonBundle\Entity\Person; -use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; /** * Resource. * * @ORM\Table(name="chill_budget.resource") + * * @ORM\Entity(repositoryClass="Chill\BudgetBundle\Repository\ResourceRepository") */ class Resource extends AbstractElement implements HasCentersInterface { /** * @ORM\Column(name="id", type="integer") + * * @ORM\Id + * * @ORM\GeneratedValue(strategy="AUTO") */ private ?int $id = null; /** * @ORM\ManyToOne(targetEntity=ResourceKind::class, inversedBy="AbstractElement") + * * @ORM\JoinColumn */ private ?ResourceKind $resource = null; public function __construct() { - $this->setStartDate(new DateTimeImmutable('today')); + $this->setStartDate(new \DateTimeImmutable('today')); } public function getCenters(): array diff --git a/src/Bundle/ChillBudgetBundle/Entity/ResourceKind.php b/src/Bundle/ChillBudgetBundle/Entity/ResourceKind.php index 4b90be5e1..78e0e6a32 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/ResourceKind.php +++ b/src/Bundle/ChillBudgetBundle/Entity/ResourceKind.php @@ -19,16 +19,21 @@ use Symfony\Component\Validator\Constraints as Assert; * Type of resource. * * @ORM\Table(name="chill_budget.resource_type", uniqueConstraints={ + * * @ORM\UniqueConstraint(name="resource_kind_unique_type_idx", fields={"kind"}) * }) + * * @ORM\Entity + * * @UniqueEntity(fields={"kind"}) */ class ResourceKind { /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") */ private ?int $id = null; @@ -40,7 +45,9 @@ class ResourceKind /** * @ORM\Column(type="string", length=255, nullable=false, options={"default": ""}) + * * @Assert\Regex(pattern="/^[a-z0-9\-_]{1,}$/", message="budget.admin.form.kind.only_alphanumeric") + * * @Assert\Length(min=3) */ private string $kind = ''; diff --git a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php index 72863163f..c2f2e4b67 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php @@ -27,20 +27,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class ChargeType extends AbstractType { - protected TranslatableStringHelperInterface $translatableStringHelper; - - private ChargeKindRepository $repository; - - private TranslatorInterface $translator; - - public function __construct( - TranslatableStringHelperInterface $translatableStringHelper, - ChargeKindRepository $repository, - TranslatorInterface $translator - ) { - $this->translatableStringHelper = $translatableStringHelper; - $this->repository = $repository; - $this->translator = $translator; + public function __construct(protected TranslatableStringHelperInterface $translatableStringHelper, private readonly ChargeKindRepository $repository, private readonly TranslatorInterface $translator) + { } public function buildForm(FormBuilderInterface $builder, array $options) diff --git a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php index 09dfd16f3..3896b0dd8 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php @@ -26,20 +26,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class ResourceType extends AbstractType { - protected TranslatableStringHelperInterface $translatableStringHelper; - - private ResourceKindRepository $repository; - - private TranslatorInterface $translator; - - public function __construct( - TranslatableStringHelperInterface $translatableStringHelper, - ResourceKindRepository $repository, - TranslatorInterface $translator - ) { - $this->translatableStringHelper = $translatableStringHelper; - $this->repository = $repository; - $this->translator = $translator; + public function __construct(protected TranslatableStringHelperInterface $translatableStringHelper, private readonly ResourceKindRepository $repository, private readonly TranslatorInterface $translator) + { } public function buildForm(FormBuilderInterface $builder, array $options) diff --git a/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php index 894230ad9..805ad865d 100644 --- a/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php @@ -15,13 +15,10 @@ use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Knp\Menu\MenuItem; use Symfony\Component\Security\Core\Security; -final class AdminMenuBuilder implements LocalMenuBuilderInterface +final readonly class AdminMenuBuilder implements LocalMenuBuilderInterface { - private Security $security; - - public function __construct(Security $security) + public function __construct(private Security $security) { - $this->security = $security; } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php b/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php index 1224c0bfe..c5d19262d 100644 --- a/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php +++ b/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php @@ -20,16 +20,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class HouseholdMenuBuilder implements LocalMenuBuilderInterface { - protected AuthorizationCheckerInterface $authorizationChecker; - - protected TranslatorInterface $translator; - - public function __construct( - AuthorizationCheckerInterface $authorizationChecker, - TranslatorInterface $translator - ) { - $this->authorizationChecker = $authorizationChecker; - $this->translator = $translator; + public function __construct(protected AuthorizationCheckerInterface $authorizationChecker, protected TranslatorInterface $translator) + { } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillBudgetBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillBudgetBundle/Menu/PersonMenuBuilder.php index 7322af849..97b10c72b 100644 --- a/src/Bundle/ChillBudgetBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillBudgetBundle/Menu/PersonMenuBuilder.php @@ -20,16 +20,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class PersonMenuBuilder implements LocalMenuBuilderInterface { - protected AuthorizationCheckerInterface $authorizationChecker; - - protected TranslatorInterface $translator; - - public function __construct( - AuthorizationCheckerInterface $authorizationChecker, - TranslatorInterface $translator - ) { - $this->authorizationChecker = $authorizationChecker; - $this->translator = $translator; + public function __construct(protected AuthorizationCheckerInterface $authorizationChecker, protected TranslatorInterface $translator) + { } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php index 9aaf68ddf..a0c23a4ad 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php @@ -17,7 +17,7 @@ use Doctrine\ORM\EntityRepository; final class ChargeKindRepository implements ChargeKindRepositoryInterface { - private EntityRepository $repository; + private readonly EntityRepository $repository; public function __construct(EntityManagerInterface $entityManager) { diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php index f92851eba..d9ec3e269 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php @@ -34,9 +34,6 @@ interface ChargeKindRepositoryInterface extends ObjectRepository public function findAllByType(string $type): array; /** - * @param int|null $limit - * @param int|null $offset - * * @return array */ public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array; diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeRepository.php index 9b89010dc..3154a772d 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ChargeRepository.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeRepository.php @@ -14,7 +14,6 @@ namespace Chill\BudgetBundle\Repository; use Chill\BudgetBundle\Entity\Charge; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; -use DateTime; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -34,7 +33,7 @@ class ChargeRepository extends ServiceEntityRepository /** * @return Charge[] */ - public function findAllByEntity(Person|Household $entity): array + public function findAllByEntity(Household|Person $entity): array { $qb = $this->createQueryBuilder('c'); @@ -46,7 +45,7 @@ class ChargeRepository extends ServiceEntityRepository return $qb->getQuery()->getResult(); } - public function findByEntityAndDate($entity, DateTime $date, $sort = null) + public function findByEntityAndDate($entity, \DateTime $date, $sort = null) { $qb = $this->createQueryBuilder('c'); diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php index 1f9f99753..723f26748 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php @@ -17,7 +17,7 @@ use Doctrine\ORM\EntityRepository; final class ResourceKindRepository implements ResourceKindRepositoryInterface { - private EntityRepository $repository; + private readonly EntityRepository $repository; public function __construct(EntityManagerInterface $entityManager) { @@ -54,7 +54,7 @@ final class ResourceKindRepository implements ResourceKindRepositoryInterface public function findOneByKind(string $kind): ?ResourceKind { - return $this->repository->findOneBy(['kind' => $kind]) ; + return $this->repository->findOneBy(['kind' => $kind]); } /** diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php index dac46ff35..950487225 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php @@ -34,9 +34,6 @@ interface ResourceKindRepositoryInterface extends ObjectRepository public function findAllByType(string $type): array; /** - * @param int|null $limit - * @param int|null $offset - * * @return list */ public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array; diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceRepository.php index 836f13e4b..2ba95e340 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ResourceRepository.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceRepository.php @@ -14,9 +14,7 @@ namespace Chill\BudgetBundle\Repository; use Chill\BudgetBundle\Entity\Resource; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; -use DateTime; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\ORM\EntityRepository; use Doctrine\Persistence\ManagerRegistry; /** @@ -33,9 +31,9 @@ class ResourceRepository extends ServiceEntityRepository } /** - * @return Resource[] + * @return resource[] */ - public function findAllByEntity(Person|Household $entity): array + public function findAllByEntity(Household|Person $entity): array { $qb = $this->createQueryBuilder('r'); @@ -47,7 +45,7 @@ class ResourceRepository extends ServiceEntityRepository return $qb->getQuery()->getResult(); } - public function findByEntityAndDate(Person|Household $entity, DateTime $date, $sort = null) + public function findByEntityAndDate(Household|Person $entity, \DateTime $date, $sort = null) { $qb = $this->createQueryBuilder('c'); diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_future_budget.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_future_budget.html.twig index 62f4e3e96..dde039194 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_future_budget.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_future_budget.html.twig @@ -1,4 +1,4 @@ -{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_elements, table_results %} +{% from '@ChillBudget/Budget/_macros.html.twig' import table_elements, table_results %}
      diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_macros.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_macros.html.twig index dfa286af4..a1fee19ce 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_macros.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_macros.html.twig @@ -1,11 +1,12 @@ -{% macro table_elements(elements, family) %} +{% macro table_elements(elements, type) %} + - - - - + + + + @@ -38,17 +39,17 @@
        {% if is_granted('CHILL_BUDGET_ELEMENT_SEE', f) %}
      • - +
      • {% endif %} {% if is_granted('CHILL_BUDGET_ELEMENT_UPDATE', f) %}
      • - +
      • {% endif %} {% if is_granted('CHILL_BUDGET_ELEMENT_DELETE', f) %}
      • - +
      • {% endif %}
      @@ -69,7 +70,7 @@
      {{ 'Budget element type'|trans }}{{ 'Amount'|trans }}{{ 'Validity period'|trans }} {{ 'Budget element type'|trans }}{{ 'Amount'|trans }}{{ 'Validity period'|trans }} 
      {% endmacro %} -{% macro table_results(actualCharges, actualResources) %} +{% macro table_results(actualCharges, actualResources, results) %} {% set totalCharges = 0 %} {% for c in actualCharges %} @@ -97,6 +98,20 @@ {{ result|format_currency('EUR') }} + {% for result in results %} + + {{ result.label }} + + {% if result.type == 'currency' %} + {{ result.result|format_currency('EUR') }} + {% elseif result.type == 'percentage' %} + {{ result.result|round(2, 'ceil') ~ '%' }} + {% else %} + {{ result.result|round(2, 'common') }} + {% endif %} + + + {% endfor %} {% endmacro %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_past_budget.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_past_budget.html.twig index ad0a12755..72c85c459 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_past_budget.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_past_budget.html.twig @@ -1,4 +1,4 @@ -{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_elements, table_results %} +{% from '@ChillBudget/Budget/_macros.html.twig' import table_elements, table_results %}
      diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/confirm_delete.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/confirm_delete.html.twig index 7a97731da..abfe9a6d5 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/confirm_delete.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/confirm_delete.html.twig @@ -18,7 +18,7 @@ {% block content %} -{{ include('ChillMainBundle:Util:confirmation_template.html.twig', +{{ include('@ChillMain/Util/confirmation_template.html.twig', { 'title' : 'Remove charge'|trans, 'confirm_question' : confirm_question, diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Household/index.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Household/index.html.twig index 1df92b297..738c5e825 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Household/index.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Household/index.html.twig @@ -1,6 +1,6 @@ {% extends "@ChillPerson/Household/layout.html.twig" %} -{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_elements, table_results %} +{% from '@ChillBudget/Budget/_macros.html.twig' import table_elements, table_results %} {% set activeRouteKey = '' %} {% set title = 'Budget for household %household%'|trans({ '%household%' : household.id } ) %} @@ -17,7 +17,7 @@ {% block content %}

      {{ title }}

      -{% include 'ChillBudgetBundle:Budget:_budget.html.twig' with { +{% include '@ChillBudget/Budget/_budget.html.twig' with { 'resources': resources, 'charges': charges, 'household': household @@ -57,7 +57,7 @@

      {{ 'Budget for %name%'|trans({'%name%': member.firstName ~ " " ~ member.lastName }) }}

      - {% include 'ChillBudgetBundle:Budget:_budget.html.twig' with { + {% include '@ChillBudget/Budget/_budget.html.twig' with { 'resources': member.getBudgetResources, 'charges': member.getBudgetCharges, 'person': member diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Person/index.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Person/index.html.twig index 18d04b889..aaf840f33 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Person/index.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Person/index.html.twig @@ -1,6 +1,6 @@ {% extends "@ChillPerson/Person/layout.html.twig" %} -{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_results %} +{% from '@ChillBudget/Budget/_macros.html.twig' import table_results %} {% set activeRouteKey = '' %} {% set title = 'Budget for %name%'|trans({ '%name%' : person.firstName ~ " " ~ person.lastName } ) %} @@ -25,7 +25,7 @@

      {{ 'Budget calculator'|trans }}

      - {{ table_results(charges, resources) }} + {{ table_results(charges, resources, results) }}
      {% if is_granted('CHILL_BUDGET_ELEMENT_CREATE', person) %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/confirm_delete.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/confirm_delete.html.twig index 5c2e80e10..afcb54c42 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/confirm_delete.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/confirm_delete.html.twig @@ -18,7 +18,7 @@ {% block content %} -{{ include('ChillMainBundle:Util:confirmation_template.html.twig', +{{ include('@ChillMain/Util/confirmation_template.html.twig', { 'title' : 'Remove resource'|trans, 'confirm_question' : confirm_question, diff --git a/src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php b/src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php index 5203f9092..f2340b8b9 100644 --- a/src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php +++ b/src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php @@ -20,24 +20,22 @@ use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use UnexpectedValueException; - class BudgetElementVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface { - public const CREATE = 'CHILL_BUDGET_ELEMENT_CREATE'; + final public const CREATE = 'CHILL_BUDGET_ELEMENT_CREATE'; - public const DELETE = 'CHILL_BUDGET_ELEMENT_DELETE'; + final public const DELETE = 'CHILL_BUDGET_ELEMENT_DELETE'; - public const ROLES = [ + final public const ROLES = [ self::CREATE, self::DELETE, self::SEE, self::UPDATE, ]; - public const SEE = 'CHILL_BUDGET_ELEMENT_SEE'; + final public const SEE = 'CHILL_BUDGET_ELEMENT_SEE'; - public const UPDATE = 'CHILL_BUDGET_ELEMENT_UPDATE'; + final public const UPDATE = 'CHILL_BUDGET_ELEMENT_UPDATE'; protected VoterHelperInterface $voter; @@ -91,6 +89,6 @@ class BudgetElementVoter extends AbstractChillVoter implements ProvideRoleHierar return false; } - throw new UnexpectedValueException('This subject is not supported, or is an element not associated with person or household'); + throw new \UnexpectedValueException('This subject is not supported, or is an element not associated with person or household'); } } diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index 1c66d4c1c..acac8a504 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -20,14 +20,11 @@ use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\ResultSetMapping; -use LogicException; -use RuntimeException; -use function count; /** * Helps to find a summary of the budget: the sum of resources and charges. */ -final class SummaryBudget implements SummaryBudgetInterface +final readonly class SummaryBudget implements SummaryBudgetInterface { private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id'; @@ -37,24 +34,8 @@ final class SummaryBudget implements SummaryBudgetInterface private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id'; - private ChargeKindRepositoryInterface $chargeKindRepository; - - private EntityManagerInterface $em; - - private ResourceKindRepositoryInterface $resourceKindRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct( - EntityManagerInterface $em, - TranslatableStringHelperInterface $translatableStringHelper, - ResourceKindRepositoryInterface $resourceKindRepository, - ChargeKindRepositoryInterface $chargeKindRepository - ) { - $this->em = $em; - $this->translatableStringHelper = $translatableStringHelper; - $this->resourceKindRepository = $resourceKindRepository; - $this->chargeKindRepository = $chargeKindRepository; + public function __construct(private EntityManagerInterface $em, private TranslatableStringHelperInterface $translatableStringHelper, private ResourceKindRepositoryInterface $resourceKindRepository, private ChargeKindRepositoryInterface $chargeKindRepository) + { } public function getSummaryForHousehold(?Household $household): array @@ -67,7 +48,7 @@ final class SummaryBudget implements SummaryBudgetInterface } $personIds = $household->getCurrentPersons()->map(static fn (Person $p) => $p->getId()); - $ids = implode(', ', array_fill(0, count($personIds), '?')); + $ids = implode(', ', array_fill(0, \count($personIds), '?')); $parameters = [...$personIds, $household->getId()]; @@ -145,7 +126,7 @@ final class SummaryBudget implements SummaryBudgetInterface $chargeKind = $this->chargeKindRepository->find($row['kind_id']); if (null === $chargeKind) { - throw new RuntimeException('charge kind not found: ' . $row['kind_id']); + throw new \RuntimeException('charge kind not found: '.$row['kind_id']); } $result[$chargeKind->getKind()] = [ 'sum' => (float) $row['sum'], @@ -161,7 +142,7 @@ final class SummaryBudget implements SummaryBudgetInterface $resourceKind = $this->resourceKindRepository->find($row['kind_id']); if (null === $resourceKind) { - throw new RuntimeException('charge kind not found: ' . $row['kind_id']); + throw new \RuntimeException('charge kind not found: '.$row['kind_id']); } $result[$resourceKind->getKind()] = [ @@ -174,7 +155,7 @@ final class SummaryBudget implements SummaryBudgetInterface return $result; default: - throw new LogicException(); + throw new \LogicException(); } } } diff --git a/src/Bundle/ChillBudgetBundle/Templating/BudgetElementTypeRender.php b/src/Bundle/ChillBudgetBundle/Templating/BudgetElementTypeRender.php index 842e6be4e..c8fa90475 100644 --- a/src/Bundle/ChillBudgetBundle/Templating/BudgetElementTypeRender.php +++ b/src/Bundle/ChillBudgetBundle/Templating/BudgetElementTypeRender.php @@ -15,21 +15,14 @@ use Chill\BudgetBundle\Entity\ChargeKind; use Chill\BudgetBundle\Entity\ResourceKind; use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; -use Symfony\Component\Templating\EngineInterface; /** * @implements ChillEntityRenderInterface */ -final class BudgetElementTypeRender implements ChillEntityRenderInterface +final readonly class BudgetElementTypeRender implements ChillEntityRenderInterface { - private EngineInterface $engine; - - private TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct(TranslatableStringHelperInterface $translatableStringHelper, EngineInterface $engine) + public function __construct(private TranslatableStringHelperInterface $translatableStringHelper, private \Twig\Environment $engine) { - $this->translatableStringHelper = $translatableStringHelper; - $this->engine = $engine; } public function renderBox($entity, array $options): string diff --git a/src/Bundle/ChillBudgetBundle/Tests/Controller/ElementControllerTest.php b/src/Bundle/ChillBudgetBundle/Tests/Controller/ElementControllerTest.php index 925506a99..52fae812e 100644 --- a/src/Bundle/ChillBudgetBundle/Tests/Controller/ElementControllerTest.php +++ b/src/Bundle/ChillBudgetBundle/Tests/Controller/ElementControllerTest.php @@ -15,10 +15,14 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** * @internal + * * @coversNothing */ final class ElementControllerTest extends WebTestCase { + /** + * @doesNotPerformAssertions + */ public function testIndex() { $client = self::createClient(); @@ -26,6 +30,9 @@ final class ElementControllerTest extends WebTestCase $crawler = $client->request('GET', '/index'); } + /** + * @doesNotPerformAssertions + */ public function testList() { $client = self::createClient(); diff --git a/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php b/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php index 77017abc5..7fb437e05 100644 --- a/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php +++ b/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php @@ -20,15 +20,12 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\PersonBundle\Entity\Person; -use DateTimeImmutable; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use ReflectionClass; -use RuntimeException; /** * @internal @@ -66,15 +63,15 @@ final class SummaryBudgetTest extends TestCase $em = $this->prophesize(EntityManagerInterface::class); $em->createNativeQuery(Argument::type('string'), Argument::type(Query\ResultSetMapping::class)) ->will(static function ($args) use ($queryResources, $queryCharges) { - if (false !== strpos($args[0], 'chill_budget.resource')) { + if (str_contains((string) $args[0], 'chill_budget.resource')) { return $queryResources->reveal(); } - if (false !== strpos($args[0], 'chill_budget.charge')) { + if (str_contains((string) $args[0], 'chill_budget.charge')) { return $queryCharges->reveal(); } - throw new RuntimeException('this query does not have a stub counterpart: ' . $args[0]); + throw new \RuntimeException('this query does not have a stub counterpart: '.$args[0]); }); $chargeRepository = $this->prophesize(ChargeKindRepositoryInterface::class); @@ -99,18 +96,18 @@ final class SummaryBudgetTest extends TestCase $translatableStringHelper->localize(Argument::type('array'))->will(static fn ($arg) => $arg[0]['fr']); $person = new Person(); - $personReflection = new ReflectionClass($person); + $personReflection = new \ReflectionClass($person); $personIdReflection = $personReflection->getProperty('id'); $personIdReflection->setAccessible(true); $personIdReflection->setValue($person, 1); $household = new Household(); - $householdReflection = new ReflectionClass($household); + $householdReflection = new \ReflectionClass($household); $householdId = $householdReflection->getProperty('id'); $householdId->setAccessible(true); $householdId->setValue($household, 1); $householdMember = (new HouseholdMember())->setPerson($person) - ->setStartDate(new DateTimeImmutable('1 month ago')); + ->setStartDate(new \DateTimeImmutable('1 month ago')); $household->addMember($householdMember); $summaryBudget = new SummaryBudget( diff --git a/src/Bundle/ChillCalendarBundle/ChillCalendarBundle.php b/src/Bundle/ChillCalendarBundle/ChillCalendarBundle.php index e2042b114..f0f20181a 100644 --- a/src/Bundle/ChillCalendarBundle/ChillCalendarBundle.php +++ b/src/Bundle/ChillCalendarBundle/ChillCalendarBundle.php @@ -21,6 +21,6 @@ class ChillCalendarBundle extends Bundle { parent::build($container); - $container->addCompilerPass(new RemoteCalendarCompilerPass()); + $container->addCompilerPass(new RemoteCalendarCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); } } diff --git a/src/Bundle/ChillCalendarBundle/Command/AzureGrantAdminConsentAndAcquireToken.php b/src/Bundle/ChillCalendarBundle/Command/AzureGrantAdminConsentAndAcquireToken.php index 400619990..c9bf020b1 100644 --- a/src/Bundle/ChillCalendarBundle/Command/AzureGrantAdminConsentAndAcquireToken.php +++ b/src/Bundle/ChillCalendarBundle/Command/AzureGrantAdminConsentAndAcquireToken.php @@ -29,22 +29,12 @@ use TheNetworg\OAuth2\Client\Provider\Azure; class AzureGrantAdminConsentAndAcquireToken extends Command { - private Azure $azure; - - private ClientRegistry $clientRegistry; - - private MachineTokenStorage $machineTokenStorage; - - public function __construct(Azure $azure, ClientRegistry $clientRegistry, MachineTokenStorage $machineTokenStorage) + public function __construct(private readonly Azure $azure, private readonly ClientRegistry $clientRegistry, private readonly MachineTokenStorage $machineTokenStorage) { parent::__construct('chill:calendar:msgraph-grant-admin-consent'); - - $this->azure = $azure; - $this->clientRegistry = $clientRegistry; - $this->machineTokenStorage = $machineTokenStorage; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { /** @var FormatterHelper $formatter */ $formatter = $this->getHelper('formatter'); @@ -76,8 +66,8 @@ class AzureGrantAdminConsentAndAcquireToken extends Command $output->writeln('Token information:'); $output->writeln($token->getToken()); - $output->writeln('Expires at: ' . $token->getExpires()); - $output->writeln('To inspect the token content, go to https://jwt.ms/#access_token=' . urlencode($token->getToken())); + $output->writeln('Expires at: '.$token->getExpires()); + $output->writeln('To inspect the token content, go to https://jwt.ms/#access_token='.urlencode($token->getToken())); return 0; } diff --git a/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php b/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php index 193b04934..177fd2375 100644 --- a/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php +++ b/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php @@ -21,11 +21,8 @@ namespace Chill\CalendarBundle\Command; use Chill\CalendarBundle\Exception\UserAbsenceSyncException; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\EventsOnUserSubscriptionCreator; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser; -use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSGraphUserRepository; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync; use Chill\MainBundle\Repository\UserRepositoryInterface; -use DateInterval; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; @@ -48,19 +45,17 @@ final class MapAndSubscribeUserCalendarCommand extends Command public function execute(InputInterface $input, OutputInterface $output): int { - $this->logger->info(self::class . ' execute command'); + $this->logger->info(self::class.' execute command'); $limit = 50; $offset = 0; - /** @var DateInterval $interval the interval before the end of the expiration */ - $interval = new DateInterval('P1D'); - $expiration = (new DateTimeImmutable('now'))->add(new DateInterval($input->getOption('subscription-duration'))); + $expiration = (new \DateTimeImmutable('now'))->add(new \DateInterval($input->getOption('subscription-duration'))); $users = $this->userRepository->findAllAsArray('fr'); $created = 0; $renewed = 0; - $this->logger->info(self::class . ' start user to get - renew', [ - 'expiration' => $expiration->format(DateTimeImmutable::ATOM), + $this->logger->info(self::class.' start user to get - renew', [ + 'expiration' => $expiration->format(\DateTimeImmutable::ATOM), ]); foreach ($users as $u) { @@ -73,8 +68,8 @@ final class MapAndSubscribeUserCalendarCommand extends Command $user = $this->userRepository->find($u['id']); if (null === $user) { - $this->logger->error("could not find user by id", ['uid' => $u['id']]); - $output->writeln("could not find user by id : " . $u['id']); + $this->logger->error('could not find user by id', ['uid' => $u['id']]); + $output->writeln('could not find user by id : '.$u['id']); continue; } @@ -83,8 +78,8 @@ final class MapAndSubscribeUserCalendarCommand extends Command // if user still does not have userid, continue if (!$this->mapCalendarToUser->hasUserId($user)) { - $this->logger->warning("user does not have a counterpart on ms api", ['userId' => $user->getId(), 'email' => $user->getEmail()]); - $output->writeln(sprintf("giving up for user with email %s and id %s", $user->getEmail(), $user->getId())); + $this->logger->warning('user does not have a counterpart on ms api', ['userId' => $user->getId(), 'email' => $user->getEmail()]); + $output->writeln(sprintf('giving up for user with email %s and id %s', $user->getEmail(), $user->getId())); continue; } @@ -94,15 +89,14 @@ final class MapAndSubscribeUserCalendarCommand extends Command try { $this->userAbsenceSync->syncUserAbsence($user); } catch (UserAbsenceSyncException $e) { - $this->logger->error("could not sync user absence", ['userId' => $user->getId(), 'email' => $user->getEmail(), 'exception' => $e->getTraceAsString(), "message" => $e->getMessage()]); - $output->writeln(sprintf("Could not sync user absence: id: %s and email: %s", $user->getId(), $user->getEmail())); - throw $e; + $this->logger->error('could not sync user absence', ['userId' => $user->getId(), 'email' => $user->getEmail(), 'exception' => $e->getTraceAsString(), 'message' => $e->getMessage()]); + $output->writeln(sprintf('Could not sync user absence: id: %s and email: %s', $user->getId(), $user->getEmail())); } // we first try to renew an existing subscription, if any. // if not, or if it fails, we try to create a new one if ($this->mapCalendarToUser->hasActiveSubscription($user)) { - $this->logger->debug(self::class . ' renew a subscription for', [ + $this->logger->debug(self::class.' renew a subscription for', [ 'userId' => $user->getId(), 'username' => $user->getUsernameCanonical(), ]); @@ -114,7 +108,7 @@ final class MapAndSubscribeUserCalendarCommand extends Command if (0 !== $expirationTs) { ++$renewed; } else { - $this->logger->warning(self::class . ' could not renew subscription for a user', [ + $this->logger->warning(self::class.' could not renew subscription for a user', [ 'userId' => $user->getId(), 'username' => $user->getUsernameCanonical(), ]); @@ -122,7 +116,7 @@ final class MapAndSubscribeUserCalendarCommand extends Command } if (!$this->mapCalendarToUser->hasActiveSubscription($user)) { - $this->logger->debug(self::class . ' create a subscription for', [ + $this->logger->debug(self::class.' create a subscription for', [ 'userId' => $user->getId(), 'username' => $user->getUsernameCanonical(), ]); @@ -134,14 +128,13 @@ final class MapAndSubscribeUserCalendarCommand extends Command if (0 !== $expirationTs) { ++$created; } else { - $this->logger->warning(self::class . ' could not create subscription for a user', [ + $this->logger->warning(self::class.' could not create subscription for a user', [ 'userId' => $user->getId(), 'username' => $user->getUsernameCanonical(), ]); } } - if (0 === $offset % $limit) { $this->em->flush(); $this->em->clear(); @@ -151,12 +144,12 @@ final class MapAndSubscribeUserCalendarCommand extends Command $this->em->flush(); $this->em->clear(); - $this->logger->warning(self::class . ' process executed', [ + $this->logger->warning(self::class.' process executed', [ 'created' => $created, 'renewed' => $renewed, ]); - $output->writeln("users synchronized"); + $output->writeln('users synchronized'); return 0; } diff --git a/src/Bundle/ChillCalendarBundle/Command/SendShortMessageOnEligibleCalendar.php b/src/Bundle/ChillCalendarBundle/Command/SendShortMessageOnEligibleCalendar.php index 6625a7adb..a027c1bc2 100644 --- a/src/Bundle/ChillCalendarBundle/Command/SendShortMessageOnEligibleCalendar.php +++ b/src/Bundle/ChillCalendarBundle/Command/SendShortMessageOnEligibleCalendar.php @@ -25,13 +25,9 @@ use Symfony\Component\Console\Output\OutputInterface; class SendShortMessageOnEligibleCalendar extends Command { - private BulkCalendarShortMessageSender $messageSender; - - public function __construct(BulkCalendarShortMessageSender $messageSender) + public function __construct(private readonly BulkCalendarShortMessageSender $messageSender) { parent::__construct(); - - $this->messageSender = $messageSender; } public function getName() @@ -39,7 +35,7 @@ class SendShortMessageOnEligibleCalendar extends Command return 'chill:calendar:send-short-messages'; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->messageSender->sendBulkMessageToEligibleCalendars(); diff --git a/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php b/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php index 5891a7be8..bfbdd2abd 100644 --- a/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php +++ b/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php @@ -26,8 +26,6 @@ use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Service\ShortMessage\ShortMessageTransporterInterface; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Repository\PersonRepository; -use DateInterval; -use DateTimeImmutable; use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberType; @@ -38,39 +36,18 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; -use UnexpectedValueException; -use function count; class SendTestShortMessageOnCalendarCommand extends Command { - private ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder; - - private PersonRepository $personRepository; - - private PhoneNumberHelperInterface $phoneNumberHelper; - - private PhoneNumberUtil $phoneNumberUtil; - - private ShortMessageTransporterInterface $transporter; - - private UserRepositoryInterface $userRepository; - public function __construct( - PersonRepository $personRepository, - PhoneNumberUtil $phoneNumberUtil, - PhoneNumberHelperInterface $phoneNumberHelper, - ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder, - ShortMessageTransporterInterface $transporter, - UserRepositoryInterface $userRepository + private readonly PersonRepository $personRepository, + private readonly PhoneNumberUtil $phoneNumberUtil, + private readonly PhoneNumberHelperInterface $phoneNumberHelper, + private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder, + private readonly ShortMessageTransporterInterface $transporter, + private readonly UserRepositoryInterface $userRepository ) { parent::__construct(); - - $this->personRepository = $personRepository; - $this->phoneNumberUtil = $phoneNumberUtil; - $this->phoneNumberHelper = $phoneNumberHelper; - $this->messageForCalendarBuilder = $messageForCalendarBuilder; - $this->transporter = $transporter; - $this->userRepository = $userRepository; } public function getName() @@ -93,20 +70,20 @@ class SendTestShortMessageOnCalendarCommand extends Command // start date $question = new Question('When will start the appointment ? (default: "1 hour") ', '1 hour'); - $startDate = new DateTimeImmutable($helper->ask($input, $output, $question)); + $startDate = new \DateTimeImmutable($helper->ask($input, $output, $question)); if (false === $startDate) { - throw new UnexpectedValueException('could not create a date with this date and time'); + throw new \UnexpectedValueException('could not create a date with this date and time'); } $calendar->setStartDate($startDate); // end date $question = new Question('How long will last the appointment ? (default: "PT30M") ', 'PT30M'); - $interval = new DateInterval($helper->ask($input, $output, $question)); + $interval = new \DateInterval($helper->ask($input, $output, $question)); if (false === $interval) { - throw new UnexpectedValueException('could not create the interval'); + throw new \UnexpectedValueException('could not create the interval'); } $calendar->setEndDate($calendar->getStartDate()->add($interval)); @@ -116,17 +93,17 @@ class SendTestShortMessageOnCalendarCommand extends Command $question ->setValidator(function ($answer): Person { if (!is_numeric($answer)) { - throw new UnexpectedValueException('the answer must be numeric'); + throw new \UnexpectedValueException('the answer must be numeric'); } if (0 >= (int) $answer) { - throw new UnexpectedValueException('the answer must be greater than zero'); + throw new \UnexpectedValueException('the answer must be greater than zero'); } $person = $this->personRepository->find((int) $answer); if (null === $person) { - throw new UnexpectedValueException('The person is not found'); + throw new \UnexpectedValueException('The person is not found'); } return $person; @@ -140,17 +117,17 @@ class SendTestShortMessageOnCalendarCommand extends Command $question ->setValidator(function ($answer): User { if (!is_numeric($answer)) { - throw new UnexpectedValueException('the answer must be numeric'); + throw new \UnexpectedValueException('the answer must be numeric'); } if (0 >= (int) $answer) { - throw new UnexpectedValueException('the answer must be greater than zero'); + throw new \UnexpectedValueException('the answer must be greater than zero'); } $user = $this->userRepository->find((int) $answer); if (null === $user) { - throw new UnexpectedValueException('The user is not found'); + throw new \UnexpectedValueException('The user is not found'); } return $user; @@ -169,13 +146,13 @@ class SendTestShortMessageOnCalendarCommand extends Command $question->setNormalizer(function ($answer): PhoneNumber { if (null === $answer) { - throw new UnexpectedValueException('The person is not found'); + throw new \UnexpectedValueException('The person is not found'); } $phone = $this->phoneNumberUtil->parse($answer, 'BE'); if (!$this->phoneNumberUtil->isPossibleNumberForType($phone, PhoneNumberType::MOBILE)) { - throw new UnexpectedValueException('Phone number si not a mobile'); + throw new \UnexpectedValueException('Phone number si not a mobile'); } return $phone; @@ -188,7 +165,7 @@ class SendTestShortMessageOnCalendarCommand extends Command $messages = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar); - if (0 === count($messages)) { + if (0 === \count($messages)) { $output->writeln('no message to send to this user'); } diff --git a/src/Bundle/ChillCalendarBundle/Controller/AdminController.php b/src/Bundle/ChillCalendarBundle/Controller/AdminController.php index 23f39a295..fceddf45a 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/AdminController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/AdminController.php @@ -23,6 +23,6 @@ class AdminController extends AbstractController */ public function indexAdminAction() { - return $this->render('ChillCalendarBundle:Admin:index.html.twig'); + return $this->render('@ChillCalendar/Admin/index.html.twig'); } } diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarAPIController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarAPIController.php index 8e0e8099c..96344103f 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarAPIController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarAPIController.php @@ -15,7 +15,6 @@ use Chill\CalendarBundle\Repository\CalendarRepository; use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Serializer\Model\Collection; -use DateTimeImmutable; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -24,11 +23,8 @@ use Symfony\Component\Routing\Annotation\Route; class CalendarAPIController extends ApiController { - private CalendarRepository $calendarRepository; - - public function __construct(CalendarRepository $calendarRepository) + public function __construct(private readonly CalendarRepository $calendarRepository) { - $this->calendarRepository = $calendarRepository; } /** @@ -45,8 +41,8 @@ class CalendarAPIController extends ApiController throw new BadRequestHttpException('You must provide a dateFrom parameter'); } - if (false === $dateFrom = DateTimeImmutable::createFromFormat( - DateTimeImmutable::ATOM, + if (false === $dateFrom = \DateTimeImmutable::createFromFormat( + \DateTimeImmutable::ATOM, $request->query->get('dateFrom') )) { throw new BadRequestHttpException('dateFrom not parsable'); @@ -56,8 +52,8 @@ class CalendarAPIController extends ApiController throw new BadRequestHttpException('You must provide a dateTo parameter'); } - if (false === $dateTo = DateTimeImmutable::createFromFormat( - DateTimeImmutable::ATOM, + if (false === $dateTo = \DateTimeImmutable::createFromFormat( + \DateTimeImmutable::ATOM, $request->query->get('dateTo') )) { throw new BadRequestHttpException('dateTo not parsable'); diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index eed693451..b6eaaf377 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php @@ -30,11 +30,8 @@ use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\ThirdPartyBundle\Entity\ThirdParty; -use DateTimeImmutable; -use Exception; use http\Exception\UnexpectedValueException; use Psr\Log\LoggerInterface; -use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Form; @@ -49,56 +46,20 @@ use Symfony\Contracts\Translation\TranslatorInterface; class CalendarController extends AbstractController { - private AccompanyingPeriodRepository $accompanyingPeriodRepository; - - private CalendarACLAwareRepositoryInterface $calendarACLAwareRepository; - - private DocGeneratorTemplateRepository $docGeneratorTemplateRepository; - - private FilterOrderHelperFactoryInterface $filterOrderHelperFactory; - - private LoggerInterface $logger; - - private PaginatorFactory $paginator; - - private PersonRepository $personRepository; - - private RemoteCalendarConnectorInterface $remoteCalendarConnector; - - private SerializerInterface $serializer; - - private TranslatableStringHelperInterface $translatableStringHelper; - - private TranslatorInterface $translator; - - private UserRepositoryInterface $userRepository; - public function __construct( - CalendarACLAwareRepositoryInterface $calendarACLAwareRepository, - DocGeneratorTemplateRepository $docGeneratorTemplateRepository, - FilterOrderHelperFactoryInterface $filterOrderHelperFactory, - LoggerInterface $logger, - PaginatorFactory $paginator, - RemoteCalendarConnectorInterface $remoteCalendarConnector, - SerializerInterface $serializer, - TranslatableStringHelperInterface $translatableStringHelper, - PersonRepository $personRepository, - AccompanyingPeriodRepository $accompanyingPeriodRepository, - UserRepositoryInterface $userRepository, - TranslatorInterface $translator + private readonly CalendarACLAwareRepositoryInterface $calendarACLAwareRepository, + private readonly DocGeneratorTemplateRepository $docGeneratorTemplateRepository, + private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory, + private readonly LoggerInterface $logger, + private readonly PaginatorFactory $paginator, + private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector, + private readonly SerializerInterface $serializer, + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly PersonRepository $personRepository, + private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, + private readonly UserRepositoryInterface $userRepository, + private readonly TranslatorInterface $translator ) { - $this->calendarACLAwareRepository = $calendarACLAwareRepository; - $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; - $this->filterOrderHelperFactory = $filterOrderHelperFactory; - $this->logger = $logger; - $this->paginator = $paginator; - $this->remoteCalendarConnector = $remoteCalendarConnector; - $this->serializer = $serializer; - $this->translatableStringHelper = $translatableStringHelper; - $this->personRepository = $personRepository; - $this->accompanyingPeriodRepository = $accompanyingPeriodRepository; - $this->userRepository = $userRepository; - $this->translator = $translator; } /** @@ -119,12 +80,12 @@ class CalendarController extends AbstractController $view = '@ChillCalendar/Calendar/confirm_deleteByPerson.html.twig'; $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]); } else { - throw new RuntimeException('nor person or accompanying period'); + throw new \RuntimeException('nor person or accompanying period'); } $form = $this->createDeleteForm($entity); - if ($request->getMethod() === Request::METHOD_DELETE) { + if (Request::METHOD_DELETE === $request->getMethod()) { $form->handleRequest($request); if ($form->isValid()) { @@ -175,7 +136,7 @@ class CalendarController extends AbstractController $view = '@ChillCalendar/Calendar/editByPerson.html.twig'; $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]); } else { - throw new RuntimeException('no person nor accompanying period'); + throw new \RuntimeException('no person nor accompanying period'); } $form = $this->createForm(CalendarType::class, $entity) @@ -185,7 +146,7 @@ class CalendarController extends AbstractController $templates = $this->docGeneratorTemplateRepository->findByEntity(Calendar::class); foreach ($templates as $template) { - $form->add('save_and_generate_doc_' . $template->getId(), SubmitType::class, [ + $form->add('save_and_generate_doc_'.$template->getId(), SubmitType::class, [ 'label' => $this->translatableStringHelper->localize($template->getName()), ]); } @@ -202,7 +163,7 @@ class CalendarController extends AbstractController } foreach ($templates as $template) { - if ($form->get('save_and_generate_doc_' . $template->getId())->isClicked()) { + if ($form->get('save_and_generate_doc_'.$template->getId())->isClicked()) { return $this->redirectToRoute('chill_docgenerator_generate_from_template', [ 'entityClassName' => Calendar::class, 'entityId' => $entity->getId(), @@ -364,7 +325,7 @@ class CalendarController extends AbstractController $form->add('save_and_upload_doc', SubmitType::class); foreach ($templates as $template) { - $form->add('save_and_generate_doc_' . $template->getId(), SubmitType::class, [ + $form->add('save_and_generate_doc_'.$template->getId(), SubmitType::class, [ 'label' => $this->translatableStringHelper->localize($template->getName()), ]); } @@ -379,12 +340,12 @@ class CalendarController extends AbstractController if ($form->get('save_and_upload_doc')->isClicked()) { return $this->redirectToRoute('chill_calendar_calendardoc_new', [ - 'id' => $entity->getId() + 'id' => $entity->getId(), ]); } foreach ($templates as $template) { - if ($form->get('save_and_generate_doc_' . $template->getId())->isClicked()) { + if ($form->get('save_and_generate_doc_'.$template->getId())->isClicked()) { return $this->redirectToRoute('chill_docgenerator_generate_from_template', [ 'entityClassName' => Calendar::class, 'entityId' => $entity->getId(), @@ -429,7 +390,7 @@ class CalendarController extends AbstractController */ public function showAction(Request $request, int $id): Response { - throw new Exception('not implemented'); + throw new \Exception('not implemented'); $view = null; $em = $this->getDoctrine()->getManager(); @@ -446,7 +407,7 @@ class CalendarController extends AbstractController } /** @var Calendar $entity */ - $entity = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($id); + $entity = $em->getRepository(Calendar::class)->find($id); if (null === $entity) { throw $this->createNotFoundException('Unable to find Calendar entity.'); @@ -490,7 +451,7 @@ class CalendarController extends AbstractController 'entity' => $entity, 'user' => $user, 'activityData' => $activityData, - //'delete_form' => $deleteForm->createView(), + // 'delete_form' => $deleteForm->createView(), ]); } @@ -535,12 +496,12 @@ class CalendarController extends AbstractController 'returnPath' => $request->query->get('returnPath', null), ]; - if ($calendar->getContext() === 'accompanying_period') { + if ('accompanying_period' === $calendar->getContext()) { $routeParams['accompanying_period_id'] = $calendar->getAccompanyingPeriod()->getId(); - } elseif ($calendar->getContext() === 'person') { + } elseif ('person' === $calendar->getContext()) { $routeParams['person_id'] = $calendar->getPerson()->getId(); } else { - throw new RuntimeException('context not found for this calendar'); + throw new \RuntimeException('context not found for this calendar'); } return $this->redirectToRoute('chill_activity_activity_new', $routeParams); @@ -549,7 +510,7 @@ class CalendarController extends AbstractController private function buildListFilterOrder(): FilterOrderHelper { $filterOrder = $this->filterOrderHelperFactory->create(self::class); - $filterOrder->addDateRange('startDate', null, new DateTimeImmutable('3 days ago'), null); + $filterOrder->addDateRange('startDate', null, new \DateTimeImmutable('3 days ago'), null); return $filterOrder->build(); } diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php index d02e55f32..6bc3245e3 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php @@ -16,7 +16,6 @@ use Chill\CalendarBundle\Entity\CalendarDoc; use Chill\CalendarBundle\Form\CalendarDocCreateType; use Chill\CalendarBundle\Form\CalendarDocEditType; use Chill\CalendarBundle\Security\Voter\CalendarDocVoter; -use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormFactoryInterface; @@ -27,40 +26,16 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Security; -use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Component\Templating\EngineInterface; -use UnexpectedValueException; -class CalendarDocController +final readonly class CalendarDocController { - private DocGeneratorTemplateRepository $docGeneratorTemplateRepository; - - private EngineInterface $engine; - - private EntityManagerInterface $entityManager; - - private FormFactoryInterface $formFactory; - - private Security $security; - - private SerializerInterface $serializer; - - private UrlGeneratorInterface $urlGenerator; - public function __construct( - DocGeneratorTemplateRepository $docGeneratorTemplateRepository, - EngineInterface $engine, - EntityManagerInterface $entityManager, - FormFactoryInterface $formFactory, - Security $security, - UrlGeneratorInterface $urlGenerator + private \Twig\Environment $engine, + private EntityManagerInterface $entityManager, + private FormFactoryInterface $formFactory, + private Security $security, + private UrlGeneratorInterface $urlGenerator, ) { - $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; - $this->engine = $engine; - $this->entityManager = $entityManager; - $this->formFactory = $formFactory; - $this->security = $security; - $this->urlGenerator = $urlGenerator; } /** @@ -91,7 +66,7 @@ class CalendarDocController break; default: - throw new UnexpectedValueException('Unsupported context'); + throw new \UnexpectedValueException('Unsupported context'); } $calendarDocDTO = new CalendarDoc\CalendarDocCreateDTO(); @@ -208,7 +183,7 @@ class CalendarDocController break; default: - throw new UnexpectedValueException('Unsupported context'); + throw new \UnexpectedValueException('Unsupported context'); } $calendarDocEditDTO = new CalendarDoc\CalendarDocEditDTO($calendarDoc); diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php index 383e24efc..2bdf393f8 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php @@ -15,21 +15,16 @@ use Chill\CalendarBundle\Repository\CalendarRangeRepository; use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Serializer\Model\Collection; -use DateTimeImmutable; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; - use Symfony\Component\Routing\Annotation\Route; class CalendarRangeAPIController extends ApiController { - private CalendarRangeRepository $calendarRangeRepository; - - public function __construct(CalendarRangeRepository $calendarRangeRepository) + public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository) { - $this->calendarRangeRepository = $calendarRangeRepository; } /** @@ -40,15 +35,15 @@ class CalendarRangeAPIController extends ApiController */ public function availableRanges(User $user, Request $request, string $_format): JsonResponse { - //return new JsonResponse(['ok' => true], 200, [], false); + // return new JsonResponse(['ok' => true], 200, [], false); $this->denyAccessUnlessGranted('ROLE_USER'); if (!$request->query->has('dateFrom')) { throw new BadRequestHttpException('You must provide a dateFrom parameter'); } - if (false === $dateFrom = DateTimeImmutable::createFromFormat( - DateTimeImmutable::ATOM, + if (false === $dateFrom = \DateTimeImmutable::createFromFormat( + \DateTimeImmutable::ATOM, $request->query->get('dateFrom') )) { throw new BadRequestHttpException('dateFrom not parsable'); @@ -58,8 +53,8 @@ class CalendarRangeAPIController extends ApiController throw new BadRequestHttpException('You must provide a dateTo parameter'); } - if (false === $dateTo = DateTimeImmutable::createFromFormat( - DateTimeImmutable::ATOM, + if (false === $dateTo = \DateTimeImmutable::createFromFormat( + \DateTimeImmutable::ATOM, $request->query->get('dateTo') )) { throw new BadRequestHttpException('dateTo not parsable'); diff --git a/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php b/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php index 3016e1660..16950b29e 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php @@ -31,21 +31,11 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; -use function in_array; class InviteApiController { - private EntityManagerInterface $entityManager; - - private MessageBusInterface $messageBus; - - private Security $security; - - public function __construct(EntityManagerInterface $entityManager, MessageBusInterface $messageBus, Security $security) + public function __construct(private readonly EntityManagerInterface $entityManager, private readonly MessageBusInterface $messageBus, private readonly Security $security) { - $this->entityManager = $entityManager; - $this->messageBus = $messageBus; - $this->security = $security; } /** @@ -69,7 +59,7 @@ class InviteApiController throw new AccessDeniedHttpException('not allowed to answer on this invitation'); } - if (!in_array($answer, Invite::STATUSES, true)) { + if (!\in_array($answer, Invite::STATUSES, true)) { throw new BadRequestHttpException('answer not valid'); } diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php index 9352e42fa..a8339137a 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php @@ -30,16 +30,8 @@ use TheNetworg\OAuth2\Client\Token\AccessToken; class RemoteCalendarConnectAzureController { - private ClientRegistry $clientRegistry; - - private OnBehalfOfUserTokenStorage $MSGraphTokenStorage; - - public function __construct( - ClientRegistry $clientRegistry, - OnBehalfOfUserTokenStorage $MSGraphTokenStorage - ) { - $this->clientRegistry = $clientRegistry; - $this->MSGraphTokenStorage = $MSGraphTokenStorage; + public function __construct(private readonly ClientRegistry $clientRegistry, private readonly OnBehalfOfUserTokenStorage $MSGraphTokenStorage) + { } /** diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php index 535dda2f5..1582a8a2d 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php @@ -19,21 +19,16 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Controller; use Chill\CalendarBundle\Messenger\Message\MSGraphChangeNotificationMessage; -use JsonException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Annotation\Route; -use const JSON_THROW_ON_ERROR; class RemoteCalendarMSGraphSyncController { - private MessageBusInterface $messageBus; - - public function __construct(MessageBusInterface $messageBus) + public function __construct(private readonly MessageBusInterface $messageBus) { - $this->messageBus = $messageBus; } /** @@ -49,8 +44,8 @@ class RemoteCalendarMSGraphSyncController } try { - $body = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR); - } catch (JsonException $e) { + $body = json_decode($request->getContent(), true, 512, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { throw new BadRequestHttpException('could not decode json', $e); } diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php index a95aa1a3f..eee828884 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php @@ -22,31 +22,20 @@ use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterfa use Chill\MainBundle\Entity\User; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Serializer\Model\Collection; -use DateTimeImmutable; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Serializer\SerializerInterface; -use function count; /** * Contains method to get events (Calendar) from remote calendar. */ class RemoteCalendarProxyController { - private PaginatorFactory $paginatorFactory; - - private RemoteCalendarConnectorInterface $remoteCalendarConnector; - - private SerializerInterface $serializer; - - public function __construct(PaginatorFactory $paginatorFactory, RemoteCalendarConnectorInterface $remoteCalendarConnector, SerializerInterface $serializer) + public function __construct(private readonly PaginatorFactory $paginatorFactory, private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector, private readonly SerializerInterface $serializer) { - $this->paginatorFactory = $paginatorFactory; - $this->remoteCalendarConnector = $remoteCalendarConnector; - $this->serializer = $serializer; } /** @@ -58,8 +47,8 @@ class RemoteCalendarProxyController throw new BadRequestHttpException('You must provide a dateFrom parameter'); } - if (false === $dateFrom = DateTimeImmutable::createFromFormat( - DateTimeImmutable::ATOM, + if (false === $dateFrom = \DateTimeImmutable::createFromFormat( + \DateTimeImmutable::ATOM, $request->query->get('dateFrom') )) { throw new BadRequestHttpException('dateFrom not parsable'); @@ -69,8 +58,8 @@ class RemoteCalendarProxyController throw new BadRequestHttpException('You must provide a dateTo parameter'); } - if (false === $dateTo = DateTimeImmutable::createFromFormat( - DateTimeImmutable::ATOM, + if (false === $dateTo = \DateTimeImmutable::createFromFormat( + \DateTimeImmutable::ATOM, $request->query->get('dateTo') )) { throw new BadRequestHttpException('dateTo not parsable'); @@ -98,8 +87,8 @@ class RemoteCalendarProxyController // in some case, we cannot paginate: we have to fetch all the items at once. We must avoid // further requests by forcing the number of items returned. - if (count($events) > $paginator->getItemsPerPage()) { - $paginator->setItemsPerPage(count($events)); + if (\count($events) > $paginator->getItemsPerPage()) { + $paginator->setItemsPerPage(\count($events)); } $collection = new Collection($events, $paginator); diff --git a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarACL.php b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarACL.php new file mode 100644 index 000000000..2ef7365ca --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarACL.php @@ -0,0 +1,54 @@ +setRole($role); + $manager->persist($r); + } + + foreach (LoadPermissionsGroup::$refs as $permissionGroupRef) { + /** @var PermissionsGroup $group */ + $group = $this->getReference($permissionGroupRef); + + foreach ($roleScopes as $scope) { + $group->addRoleScope($scope); + } + } + + $manager->flush(); + } + + public function getOrder(): int + { + return 16000; + } +} diff --git a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php index 805386669..71277cd8a 100644 --- a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php +++ b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php @@ -18,7 +18,6 @@ use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Entity\PostalCode; use Chill\MainBundle\Repository\UserRepository; -use DateTimeImmutable; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; @@ -29,12 +28,8 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere { public static array $references = []; - private UserRepository $userRepository; - - public function __construct( - UserRepository $userRepository - ) { - $this->userRepository = $userRepository; + public function __construct(private readonly UserRepository $userRepository) + { } public static function getGroups(): array @@ -49,10 +44,6 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere public function load(ObjectManager $manager): void { - $arr = range(-50, 50); - - echo "Creating calendar range ('plage de disponibilités')\n"; - $users = $this->userRepository->findAll(); $location = (new Location()) @@ -73,6 +64,8 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere $manager->persist($type); $manager->persist($location); + $now = new \DateTimeImmutable(); + $days = [ '2021-08-23', '2021-08-24', @@ -82,8 +75,12 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere '2021-08-31', '2021-09-01', '2021-09-02', - (new DateTimeImmutable('tomorrow'))->format('Y-m-d'), - (new DateTimeImmutable('today'))->format('Y-m-d'), + (new \DateTimeImmutable('tomorrow'))->format('Y-m-d'), + (new \DateTimeImmutable('today'))->format('Y-m-d'), + $now->add(new \DateInterval('P7D'))->format('Y-m-d'), + $now->add(new \DateInterval('P8D'))->format('Y-m-d'), + $now->add(new \DateInterval('P9D'))->format('Y-m-d'), + $now->add(new \DateInterval('P10D'))->format('Y-m-d'), ]; $hours = [ @@ -96,9 +93,9 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere foreach ($users as $u) { foreach ($days as $d) { foreach ($hours as $h) { - $event = $d . ' ' . $h; - $startEvent = new DateTimeImmutable($event); - $endEvent = new DateTimeImmutable($event . ' + 1 hours'); + $event = $d.' '.$h; + $startEvent = new \DateTimeImmutable($event); + $endEvent = new \DateTimeImmutable($event.' + 1 hours'); $calendarRange = (new CalendarRange()) ->setUser($u) ->setStartDate($startEvent) diff --git a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php index bc5de7f70..d7e552d5d 100644 --- a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php +++ b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php @@ -39,12 +39,12 @@ class LoadCancelReason extends Fixture implements FixtureGroupInterface ]; foreach ($arr as $a) { - echo 'Creating calendar cancel reason : ' . $a['name'] . "\n"; + echo 'Creating calendar cancel reason : '.$a['name']."\n"; $cancelReason = (new CancelReason()) ->setCanceledBy($a['name']) ->setActive(true); $manager->persist($cancelReason); - $reference = 'CancelReason_' . $a['name']; + $reference = 'CancelReason_'.$a['name']; $this->addReference($reference, $cancelReason); static::$references[] = $reference; } diff --git a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadInvite.php b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadInvite.php index ba325e296..f677a4283 100644 --- a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadInvite.php +++ b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadInvite.php @@ -46,12 +46,12 @@ class LoadInvite extends Fixture implements FixtureGroupInterface ]; foreach ($arr as $a) { - echo 'Creating calendar invite : ' . $a['name']['fr'] . "\n"; + echo 'Creating calendar invite : '.$a['name']['fr']."\n"; $invite = (new Invite()) ->setStatus($a['status']) ->setUser($this->getRandomUser()); $manager->persist($invite); - $reference = 'Invite_' . $a['name']['fr']; + $reference = 'Invite_'.$a['name']['fr']; $this->addReference($reference, $invite); static::$references[] = $reference; } diff --git a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php index c848366bb..5aa8d8507 100644 --- a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php +++ b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php @@ -31,7 +31,7 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); $loader->load('services/exports.yaml'); $loader->load('services/controller.yml'); diff --git a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php index 79219a6e0..32308a50c 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php +++ b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php @@ -24,28 +24,26 @@ use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\ThirdPartyBundle\Entity\ThirdParty; -use DateInterval; -use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\ReadableCollection; +use Doctrine\Common\Collections\Selectable; use Doctrine\ORM\Mapping as ORM; -use LogicException; use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints\NotBlank; - use Symfony\Component\Validator\Constraints\Range; use Symfony\Component\Validator\Mapping\ClassMetadata; -use function in_array; /** * @ORM\Table( * name="chill_calendar.calendar", * uniqueConstraints={@ORM\UniqueConstraint(name="idx_calendar_remote", columns={"remoteId"}, options={"where": "remoteId <> ''"})} * ) + * * @ORM\Entity + * * @Serializer\DiscriminatorMap(typeProperty="type", mapping={ * "chill_calendar_calendar": Calendar::class * }) @@ -58,20 +56,20 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente use TrackUpdateTrait; - public const SMS_CANCEL_PENDING = 'sms_cancel_pending'; + final public const SMS_CANCEL_PENDING = 'sms_cancel_pending'; - public const SMS_PENDING = 'sms_pending'; + final public const SMS_PENDING = 'sms_pending'; - public const SMS_SENT = 'sms_sent'; + final public const SMS_SENT = 'sms_sent'; - public const STATUS_CANCELED = 'canceled'; + final public const STATUS_CANCELED = 'canceled'; /** * @deprecated */ - public const STATUS_MOVED = 'moved'; + final public const STATUS_MOVED = 'moved'; - public const STATUS_VALID = 'valid'; + final public const STATUS_VALID = 'valid'; /** * a list of invite which have been added during this session. @@ -93,6 +91,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente /** * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod", inversedBy="calendars") + * * @Serializer\Groups({"calendar:read", "read"}) */ private ?AccompanyingPeriod $accompanyingPeriod = null; @@ -104,6 +103,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente /** * @ORM\OneToOne(targetEntity="CalendarRange", inversedBy="calendar") + * * @Serializer\Groups({"calendar:read", "read"}) */ private ?CalendarRange $calendarRange = null; @@ -115,6 +115,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente /** * @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_") + * * @Serializer\Groups({"calendar:read", "read", "docgen:read"}) */ private CommentEmbeddable $comment; @@ -125,22 +126,28 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente private int $dateTimeVersion = 0; /** - * @var Collection + * @var Collection + * * @ORM\OneToMany(targetEntity=CalendarDoc::class, mappedBy="calendar", orphanRemoval=true) */ private Collection $documents; /** * @ORM\Column(type="datetime_immutable", nullable=false) + * * @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"}) + * * @Assert\NotNull(message="calendar.An end date is required") */ - private ?DateTimeImmutable $endDate = null; + private ?\DateTimeImmutable $endDate = null; /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") + * * @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"}) */ private ?int $id = null; @@ -152,57 +159,80 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente * orphanRemoval=true, * cascade={"persist", "remove", "merge", "detach"} * ) + * * @ORM\JoinTable(name="chill_calendar.calendar_to_invites") + * * @Serializer\Groups({"read", "docgen:read"}) + * + * @var Collection&Selectable */ - private Collection $invites; + private Collection&Selectable $invites; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location") + * * @Serializer\Groups({"read", "docgen:read"}) + * * @Assert\NotNull(message="calendar.A location is required") */ private ?Location $location = null; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") + * * @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"}) + * * @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"}) + * * @Assert\NotNull(message="calendar.A main user is mandatory") */ private ?User $mainUser = null; /** * @ORM\ManyToOne(targetEntity=Person::class) + * * @ORM\JoinColumn(nullable=true) */ private ?Person $person = null; /** * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person", inversedBy="calendars") + * * @ORM\JoinTable(name="chill_calendar.calendar_to_persons") + * * @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"}) + * * @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"}) + * * @Assert\Count(min=1, minMessage="calendar.At least {{ limit }} person is required.") + * + * @var Collection */ private Collection $persons; /** * @ORM\Embedded(class=PrivateCommentEmbeddable::class, columnPrefix="privateComment_") + * * @Serializer\Groups({"calendar:read"}) */ private PrivateCommentEmbeddable $privateComment; /** + * @var Collection + * * @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty") + * * @ORM\JoinTable(name="chill_calendar.calendar_to_thirdparties") + * * @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"}) + * * @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"}) */ private Collection $professionals; /** * @ORM\Column(type="boolean", nullable=true) + * * @Serializer\Groups({"docgen:read"}) */ private ?bool $sendSMS = false; @@ -214,21 +244,27 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente /** * @ORM\Column(type="datetime_immutable", nullable=false) + * * @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"}) + * * @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"}) + * * @Assert\NotNull(message="calendar.A start date is required") */ - private ?DateTimeImmutable $startDate = null; + private ?\DateTimeImmutable $startDate = null; /** * @ORM\Column(type="string", length=255, nullable=false, options={"default": "valid"}) + * * @Serializer\Groups({"calendar:read", "read", "calendar:light"}) + * * @Serializer\Context(normalizationContext={"read"}, groups={"calendar:light"}) */ private string $status = self::STATUS_VALID; /** * @ORM\Column(type="boolean", nullable=true) + * * @Serializer\Groups({"docgen:read"}) */ private ?bool $urgent = false; @@ -261,7 +297,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente public function addInvite(Invite $invite): self { if ($invite->getCalendar() instanceof Calendar && $invite->getCalendar() !== $this) { - throw new LogicException('Not allowed to move an invitation to another Calendar'); + throw new \LogicException('Not allowed to move an invitation to another Calendar'); } $this->invites[] = $invite; @@ -317,16 +353,11 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente public function getCenters(): ?iterable { - switch ($this->getContext()) { - case 'person': - return [$this->getPerson()->getCenter()]; - - case 'accompanying_period': - return $this->getAccompanyingPeriod()->getCenters(); - - default: - throw new LogicException('context not supported: ' . $this->getContext()); - } + return match ($this->getContext()) { + 'person' => [$this->getPerson()->getCenter()], + 'accompanying_period' => $this->getAccompanyingPeriod()->getCenters(), + default => throw new \LogicException('context not supported: '.$this->getContext()), + }; } public function getComment(): CommentEmbeddable @@ -339,11 +370,11 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente */ public function getContext(): ?string { - if ($this->getAccompanyingPeriod() !== null) { + if (null !== $this->getAccompanyingPeriod()) { return 'accompanying_period'; } - if ($this->getPerson() !== null) { + if (null !== $this->getPerson()) { return 'person'; } @@ -366,16 +397,16 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente /** * @Serializer\Groups({"docgen:read"}) */ - public function getDuration(): ?DateInterval + public function getDuration(): ?\DateInterval { - if ($this->getStartDate() === null || $this->getEndDate() === null) { + if (null === $this->getStartDate() || null === $this->getEndDate()) { return null; } return $this->getStartDate()->diff($this->getEndDate()); } - public function getEndDate(): ?DateTimeImmutable + public function getEndDate(): ?\DateTimeImmutable { return $this->endDate; } @@ -454,7 +485,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente $personsNotAssociated = []; foreach ($this->persons as $person) { - if (!in_array($person, $this->getPersonsAssociated(), true)) { + if (!\in_array($person, $this->getPersonsAssociated(), true)) { $personsNotAssociated[] = $person; } } @@ -488,7 +519,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente return $this->smsStatus; } - public function getStartDate(): ?DateTimeImmutable + public function getStartDate(): ?\DateTimeImmutable { return $this->startDate; } @@ -510,6 +541,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente /** * @return ReadableCollection<(int|string), User> + * * @Serializer\Groups({"calendar:read", "read"}) */ public function getUsers(): ReadableCollection @@ -559,7 +591,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente public function removeDocument(CalendarDoc $calendarDoc): self { if ($calendarDoc->getCalendar() !== $this) { - throw new LogicException('cannot remove document of another calendar'); + throw new \LogicException('cannot remove document of another calendar'); } return $this; @@ -653,7 +685,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente return $this; } - public function setEndDate(DateTimeImmutable $endDate): self + public function setEndDate(\DateTimeImmutable $endDate): self { if (null === $this->endDate || $this->endDate->getTimestamp() !== $endDate->getTimestamp()) { $this->increaseaDatetimeVersion(); @@ -711,7 +743,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente return $this; } - public function setStartDate(DateTimeImmutable $startDate): self + public function setStartDate(\DateTimeImmutable $startDate): self { if (null === $this->startDate || $this->startDate->getTimestamp() !== $startDate->getTimestamp()) { $this->increaseaDatetimeVersion(); @@ -726,7 +758,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente { $this->status = $status; - if (self::STATUS_CANCELED === $status && $this->getSmsStatus() === self::SMS_SENT) { + if (self::STATUS_CANCELED === $status && self::SMS_SENT === $this->getSmsStatus()) { $this->setSmsStatus(self::SMS_CANCEL_PENDING); } diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php index 7012b64e0..5ea958fab 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php @@ -22,6 +22,7 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity + * * @ORM\Table( * name="chill_calendar.calendar_doc", * ) @@ -34,6 +35,7 @@ class CalendarDoc implements TrackCreationInterface, TrackUpdateInterface /** * @ORM\ManyToOne(targetEntity=Calendar::class, inversedBy="documents") + * * @ORM\JoinColumn(nullable=false) */ private Calendar $calendar; @@ -45,27 +47,26 @@ class CalendarDoc implements TrackCreationInterface, TrackUpdateInterface /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") */ private ?int $id = null; - /** - * @ORM\ManyToOne(targetEntity=StoredObject::class, cascade={"persist"}) - * @ORM\JoinColumn(nullable=false) - */ - private ?StoredObject $storedObject; - /** * @ORM\Column(type="boolean", nullable=false, options={"default": false}) */ private bool $trackDateTimeVersion = false; - public function __construct(Calendar $calendar, ?StoredObject $storedObject) + public function __construct(Calendar $calendar, /** + * @ORM\ManyToOne(targetEntity=StoredObject::class, cascade={"persist"}) + * + * @ORM\JoinColumn(nullable=false) + */ + private ?StoredObject $storedObject) { $this->setCalendar($calendar); - - $this->storedObject = $storedObject; $this->datetimeVersion = $calendar->getDateTimeVersion(); } @@ -112,8 +113,6 @@ class CalendarDoc implements TrackCreationInterface, TrackUpdateInterface /** * @internal use @see{Calendar::removeDocument} instead - * - * @param Calendar $calendar */ public function setCalendar(?Calendar $calendar): CalendarDoc { diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php index b7209e46f..ff4e10402 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php @@ -18,12 +18,14 @@ class CalendarDocCreateDTO { /** * @Assert\NotNull + * * @Assert\Valid */ public ?StoredObject $doc = null; /** * @Assert\NotBlank + * * @Assert\NotNull */ public ?string $title = ''; diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocEditDTO.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocEditDTO.php index ff57d9876..2e8970db6 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocEditDTO.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocEditDTO.php @@ -24,6 +24,7 @@ class CalendarDocEditDTO /** * @Assert\NotBlank + * * @Assert\NotNull */ public ?string $title = ''; diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php index 5dbc4286f..0c46db57c 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php @@ -17,7 +17,6 @@ use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\User; -use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; @@ -27,6 +26,7 @@ use Symfony\Component\Validator\Constraints as Assert; * name="chill_calendar.calendar_range", * uniqueConstraints={@ORM\UniqueConstraint(name="idx_calendar_range_remote", columns={"remoteId"}, options={"where": "remoteId <> ''"})} * ) + * * @ORM\Entity */ class CalendarRange implements TrackCreationInterface, TrackUpdateInterface @@ -44,37 +44,49 @@ class CalendarRange implements TrackCreationInterface, TrackUpdateInterface /** * @ORM\Column(type="datetime_immutable", nullable=false) + * * @Groups({"read", "write", "calendar:read"}) + * * @Assert\NotNull */ - private ?DateTimeImmutable $endDate = null; + private ?\DateTimeImmutable $endDate = null; /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") + * * @Groups({"read"}) */ - private $id; + private ?int $id = null; /** * @ORM\ManyToOne(targetEntity=Location::class) + * * @ORM\JoinColumn(nullable=false) + * * @Groups({"read", "write", "calendar:read"}) + * * @Assert\NotNull */ private ?Location $location = null; /** * @ORM\Column(type="datetime_immutable", nullable=false) + * * @groups({"read", "write", "calendar:read"}) + * * @Assert\NotNull */ - private ?DateTimeImmutable $startDate = null; + private ?\DateTimeImmutable $startDate = null; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") + * * @Groups({"read", "write", "calendar:read"}) + * * @Assert\NotNull */ private ?User $user = null; @@ -84,7 +96,7 @@ class CalendarRange implements TrackCreationInterface, TrackUpdateInterface return $this->calendar; } - public function getEndDate(): ?DateTimeImmutable + public function getEndDate(): ?\DateTimeImmutable { return $this->endDate; } @@ -99,7 +111,7 @@ class CalendarRange implements TrackCreationInterface, TrackUpdateInterface return $this->location; } - public function getStartDate(): ?DateTimeImmutable + public function getStartDate(): ?\DateTimeImmutable { return $this->startDate; } @@ -117,7 +129,7 @@ class CalendarRange implements TrackCreationInterface, TrackUpdateInterface $this->calendar = $calendar; } - public function setEndDate(DateTimeImmutable $endDate): self + public function setEndDate(\DateTimeImmutable $endDate): self { $this->endDate = $endDate; @@ -131,7 +143,7 @@ class CalendarRange implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function setStartDate(DateTimeImmutable $startDate): self + public function setStartDate(\DateTimeImmutable $startDate): self { $this->startDate = $startDate; diff --git a/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php b/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php index a262842c2..cff54e70b 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php @@ -16,37 +16,40 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Table(name="chill_calendar.cancel_reason") + * * @ORM\Entity(repositoryClass=CancelReasonRepository::class) */ class CancelReason { - public const CANCELEDBY_DONOTCOUNT = 'CANCELEDBY_DONOTCOUNT'; + final public const CANCELEDBY_DONOTCOUNT = 'CANCELEDBY_DONOTCOUNT'; - public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON'; + final public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON'; - public const CANCELEDBY_USER = 'CANCELEDBY_USER'; + final public const CANCELEDBY_USER = 'CANCELEDBY_USER'; /** * @ORM\Column(type="boolean") */ - private $active; + private ?bool $active = null; /** * @ORM\Column(type="string", length=255) */ - private $canceledBy; + private ?string $canceledBy = null; /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") */ - private $id; + private ?int $id = null; /** * @ORM\Column(type="json") */ - private $name = []; + private array $name = []; public function getActive(): ?bool { diff --git a/src/Bundle/ChillCalendarBundle/Entity/Invite.php b/src/Bundle/ChillCalendarBundle/Entity/Invite.php index c2d79aff2..48fe865b0 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/Invite.php +++ b/src/Bundle/ChillCalendarBundle/Entity/Invite.php @@ -17,7 +17,6 @@ use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait; use Chill\MainBundle\Entity\User; use Doctrine\ORM\Mapping as ORM; -use LogicException; use Symfony\Component\Serializer\Annotation as Serializer; /** @@ -30,6 +29,7 @@ use Symfony\Component\Serializer\Annotation as Serializer; * name="chill_calendar.invite", * uniqueConstraints={@ORM\UniqueConstraint(name="idx_calendar_invite_remote", columns={"remoteId"}, options={"where": "remoteId <> ''"})} * ) + * * @ORM\Entity */ class Invite implements TrackUpdateInterface, TrackCreationInterface @@ -40,23 +40,23 @@ class Invite implements TrackUpdateInterface, TrackCreationInterface use TrackUpdateTrait; - public const ACCEPTED = 'accepted'; + final public const ACCEPTED = 'accepted'; - public const DECLINED = 'declined'; + final public const DECLINED = 'declined'; - public const PENDING = 'pending'; + final public const PENDING = 'pending'; /** * all statuses in one const. */ - public const STATUSES = [ + final public const STATUSES = [ self::ACCEPTED, self::DECLINED, self::PENDING, self::TENTATIVELY_ACCEPTED, ]; - public const TENTATIVELY_ACCEPTED = 'tentative'; + final public const TENTATIVELY_ACCEPTED = 'tentative'; /** * @ORM\ManyToOne(targetEntity=Calendar::class, inversedBy="invites") @@ -65,21 +65,27 @@ class Invite implements TrackUpdateInterface, TrackCreationInterface /** * @ORM\Id + * * @ORM\GeneratedValue + * * @ORM\Column(type="integer") + * * @Serializer\Groups(groups={"calendar:read", "read"}) */ private ?int $id = null; /** * @ORM\Column(type="text", nullable=false, options={"default": "pending"}) + * * @Serializer\Groups(groups={"calendar:read", "read", "docgen:read"}) */ private string $status = self::PENDING; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") + * * @ORM\JoinColumn(nullable=false) + * * @Serializer\Groups(groups={"calendar:read", "read", "docgen:read"}) */ private ?User $user = null; @@ -122,7 +128,7 @@ class Invite implements TrackUpdateInterface, TrackCreationInterface public function setUser(?User $user): self { if ($user instanceof User && $this->user instanceof User && $user !== $this->user) { - throw new LogicException('Not allowed to associate an invite to a different user'); + throw new \LogicException('Not allowed to associate an invite to a different user'); } $this->user = $user; diff --git a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php index 298a1ef9a..2f263d66a 100644 --- a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php +++ b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php @@ -15,15 +15,10 @@ use Chill\ActivityBundle\Entity\Activity; use Doctrine\Persistence\Event\LifecycleEventArgs; use Symfony\Component\HttpFoundation\RequestStack; -use function array_key_exists; - class ListenToActivityCreate { - private RequestStack $requestStack; - - public function __construct(RequestStack $requestStack) + public function __construct(private readonly RequestStack $requestStack) { - $this->requestStack = $requestStack; } public function postPersist(Activity $activity, LifecycleEventArgs $event): void @@ -38,7 +33,7 @@ class ListenToActivityCreate if ($request->query->has('activityData')) { $activityData = $request->query->get('activityData'); - if (array_key_exists('calendarId', $activityData)) { + if (\array_key_exists('calendarId', $activityData)) { $calendarId = $activityData['calendarId']; // Attach the activity to the calendar diff --git a/src/Bundle/ChillCalendarBundle/Exception/UserAbsenceSyncException.php b/src/Bundle/ChillCalendarBundle/Exception/UserAbsenceSyncException.php index a5e5a679a..7466f93a4 100644 --- a/src/Bundle/ChillCalendarBundle/Exception/UserAbsenceSyncException.php +++ b/src/Bundle/ChillCalendarBundle/Exception/UserAbsenceSyncException.php @@ -13,7 +13,7 @@ namespace Chill\CalendarBundle\Exception; class UserAbsenceSyncException extends \LogicException { - public function __construct(string $message = "", int $code = 20_230_706, ?\Throwable $previous = null) + public function __construct(string $message = '', int $code = 20_230_706, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php index 1b1b170b8..43354207c 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php @@ -15,23 +15,13 @@ use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Templating\Entity\UserRender; -use Closure; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; -final class AgentAggregator implements AggregatorInterface +final readonly class AgentAggregator implements AggregatorInterface { - private UserRender $userRender; - - private UserRepository $userRepository; - - public function __construct( - UserRepository $userRepository, - UserRender $userRender - ) { - $this->userRepository = $userRepository; - $this->userRender = $userRender; + public function __construct(private UserRepository $userRepository, private UserRender $userRender) + { } public function addRole(): ?string @@ -41,7 +31,7 @@ final class AgentAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('caluser', $qb->getAllAliases(), true)) { + if (!\in_array('caluser', $qb->getAllAliases(), true)) { $qb->join('cal.mainUser', 'caluser'); } @@ -58,12 +48,13 @@ final class AgentAggregator implements AggregatorInterface { // no form } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php index a9967c470..7fe83726c 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php @@ -15,23 +15,13 @@ use Chill\CalendarBundle\Export\Declarations; use Chill\CalendarBundle\Repository\CancelReasonRepository; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Closure; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class CancelReasonAggregator implements AggregatorInterface { - private CancelReasonRepository $cancelReasonRepository; - - private TranslatableStringHelper $translatableStringHelper; - - public function __construct( - CancelReasonRepository $cancelReasonRepository, - TranslatableStringHelper $translatableStringHelper - ) { - $this->cancelReasonRepository = $cancelReasonRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct(private readonly CancelReasonRepository $cancelReasonRepository, private readonly TranslatableStringHelper $translatableStringHelper) + { } public function addRole(): ?string @@ -42,7 +32,7 @@ class CancelReasonAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { // TODO: still needs to take into account calendars without a cancel reason somehow - if (!in_array('calcancel', $qb->getAllAliases(), true)) { + if (!\in_array('calcancel', $qb->getAllAliases(), true)) { $qb->join('cal.cancelReason', 'calcancel'); } @@ -59,12 +49,13 @@ class CancelReasonAggregator implements AggregatorInterface { // no form } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php index 51291b49e..1d1c4dca9 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php @@ -12,26 +12,22 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Export\Aggregator; use Chill\CalendarBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\UserJobRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Closure; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; -final class JobAggregator implements AggregatorInterface +final readonly class JobAggregator implements AggregatorInterface { - private UserJobRepository $jobRepository; - - private TranslatableStringHelper $translatableStringHelper; + private const PREFIX = 'cal_agg_job'; public function __construct( - UserJobRepository $jobRepository, - TranslatableStringHelper $translatableStringHelper + private UserJobRepository $jobRepository, + private TranslatableStringHelper $translatableStringHelper ) { - $this->jobRepository = $jobRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -41,12 +37,28 @@ final class JobAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('caluser', $qb->getAllAliases(), true)) { - $qb->join('cal.mainUser', 'caluser'); - } + $p = self::PREFIX; - $qb->addSelect('IDENTITY(caluser.userJob) as job_aggregator'); - $qb->addGroupBy('job_aggregator'); + $qb + ->leftJoin('cal.mainUser', "{$p}_user") + ->leftJoin( + UserJobHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // job_at based on cal.startDate + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'cal.startDate'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'cal.startDate') + ) + ) + ) + ->addSelect("IDENTITY({$p}_history.job) AS {$p}_select") + ->addGroupBy("{$p}_select"); } public function applyOn(): string @@ -56,14 +68,14 @@ final class JobAggregator implements AggregatorInterface public function buildForm(FormBuilderInterface $builder) { - // no form } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { @@ -74,7 +86,9 @@ final class JobAggregator implements AggregatorInterface return ''; } - $j = $this->jobRepository->find($value); + if (null === $j = $this->jobRepository->find($value)) { + return ''; + } return $this->translatableStringHelper->localize( $j->getLabel() @@ -84,11 +98,11 @@ final class JobAggregator implements AggregatorInterface public function getQueryKeys($data): array { - return ['job_aggregator']; + return [self::PREFIX.'_select']; } public function getTitle(): string { - return 'Group calendars by agent job'; + return 'export.aggregator.calendar.agent_job.Group calendars by agent job'; } } diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php index 952d0c5d5..aca3e654b 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php @@ -14,19 +14,13 @@ namespace Chill\CalendarBundle\Export\Aggregator; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\LocationRepository; -use Closure; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; -final class LocationAggregator implements AggregatorInterface +final readonly class LocationAggregator implements AggregatorInterface { - private LocationRepository $locationRepository; - - public function __construct( - LocationRepository $locationRepository - ) { - $this->locationRepository = $locationRepository; + public function __construct(private LocationRepository $locationRepository) + { } public function addRole(): ?string @@ -36,7 +30,7 @@ final class LocationAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('calloc', $qb->getAllAliases(), true)) { + if (!\in_array('calloc', $qb->getAllAliases(), true)) { $qb->join('cal.location', 'calloc'); } $qb->addSelect('IDENTITY(cal.location) as location_aggregator'); @@ -52,12 +46,13 @@ final class LocationAggregator implements AggregatorInterface { // no form } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php index a9b369af0..1f49ff723 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php @@ -15,23 +15,13 @@ use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\LocationTypeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Closure; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; -final class LocationTypeAggregator implements AggregatorInterface +final readonly class LocationTypeAggregator implements AggregatorInterface { - private LocationTypeRepository $locationTypeRepository; - - private TranslatableStringHelper $translatableStringHelper; - - public function __construct( - LocationTypeRepository $locationTypeRepository, - TranslatableStringHelper $translatableStringHelper - ) { - $this->locationTypeRepository = $locationTypeRepository; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct(private LocationTypeRepository $locationTypeRepository, private TranslatableStringHelper $translatableStringHelper) + { } public function addRole(): ?string @@ -41,7 +31,7 @@ final class LocationTypeAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('calloc', $qb->getAllAliases(), true)) { + if (!\in_array('calloc', $qb->getAllAliases(), true)) { $qb->join('cal.location', 'calloc'); } @@ -58,12 +48,13 @@ final class LocationTypeAggregator implements AggregatorInterface { // no form } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php index b3c2aaf19..6bf65b8ef 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php @@ -13,7 +13,6 @@ namespace Chill\CalendarBundle\Export\Aggregator; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; -use Closure; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -40,12 +39,13 @@ class MonthYearAggregator implements AggregatorInterface { // No form needed } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php index 01874b0e2..d298e63a4 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php @@ -12,26 +12,22 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Export\Aggregator; use Chill\CalendarBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Closure; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; -final class ScopeAggregator implements AggregatorInterface +final readonly class ScopeAggregator implements AggregatorInterface { - private ScopeRepository $scopeRepository; - - private TranslatableStringHelper $translatableStringHelper; + private const PREFIX = 'cal_agg_scope'; public function __construct( - ScopeRepository $scopeRepository, - TranslatableStringHelper $translatableStringHelper + private ScopeRepository $scopeRepository, + private TranslatableStringHelper $translatableStringHelper ) { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -41,12 +37,28 @@ final class ScopeAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('caluser', $qb->getAllAliases(), true)) { - $qb->join('cal.mainUser', 'caluser'); - } + $p = self::PREFIX; - $qb->addSelect('IDENTITY(caluser.mainScope) as scope_aggregator'); - $qb->addGroupBy('scope_aggregator'); + $qb + ->leftJoin('cal.mainUser', "{$p}_user") + ->leftJoin( + UserScopeHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // scope_at based on cal.startDate + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'cal.startDate'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'cal.startDate') + ) + ) + ) + ->addSelect("IDENTITY({$p}_history.scope) AS {$p}_select") + ->addGroupBy("{$p}_select"); } public function applyOn(): string @@ -56,14 +68,14 @@ final class ScopeAggregator implements AggregatorInterface public function buildForm(FormBuilderInterface $builder) { - // no form } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { @@ -74,7 +86,9 @@ final class ScopeAggregator implements AggregatorInterface return ''; } - $s = $this->scopeRepository->find($value); + if (null === $s = $this->scopeRepository->find($value)) { + return ''; + } return $this->translatableStringHelper->localize( $s->getName() @@ -84,11 +98,11 @@ final class ScopeAggregator implements AggregatorInterface public function getQueryKeys($data): array { - return ['scope_aggregator']; + return [self::PREFIX.'_select']; } public function getTitle(): string { - return 'Group calendars by agent scope'; + return 'export.aggregator.calendar.agent_scope.Group calendars by agent scope'; } } diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php index 66ab8b42e..47801add3 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php @@ -20,20 +20,14 @@ namespace Chill\CalendarBundle\Export\Aggregator; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Export\AggregatorInterface; -use Closure; use Doctrine\ORM\QueryBuilder; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Contracts\Translation\TranslatorInterface; class UrgencyAggregator implements AggregatorInterface { - private TranslatorInterface $translator; - - public function __construct( - TranslatorInterface $translator - ) { - $this->translator = $translator; + public function __construct(private readonly TranslatorInterface $translator) + { } public function addRole(): ?string @@ -56,28 +50,24 @@ class UrgencyAggregator implements AggregatorInterface { // no form } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { return 'Urgency'; } - switch ($value) { - case true: - return $this->translator->trans('is urgent'); - - case false: - return $this->translator->trans('is not urgent'); - - default: - throw new LogicException(sprintf('The value %s is not valid', $value)); - } + return match ($value) { + true => $this->translator->trans('is urgent'), + false => $this->translator->trans('is not urgent'), + default => throw new \LogicException(sprintf('The value %s is not valid', $value)), + }; }; } diff --git a/src/Bundle/ChillCalendarBundle/Export/Declarations.php b/src/Bundle/ChillCalendarBundle/Export/Declarations.php index 0df90749c..a007424e5 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Declarations.php +++ b/src/Bundle/ChillCalendarBundle/Export/Declarations.php @@ -16,5 +16,5 @@ namespace Chill\CalendarBundle\Export; */ abstract class Declarations { - public const CALENDAR_TYPE = 'calendar'; + final public const CALENDAR_TYPE = 'calendar'; } diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php b/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php index e0391948e..9e7c5e367 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php @@ -13,11 +13,11 @@ namespace Chill\CalendarBundle\Export\Export; use Chill\CalendarBundle\Export\Declarations; use Chill\CalendarBundle\Repository\CalendarRepository; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Closure; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -25,17 +25,16 @@ use Symfony\Component\Validator\Exception\LogicException; class CountCalendars implements ExportInterface, GroupedExportInterface { - private CalendarRepository $calendarRepository; - - public function __construct(CalendarRepository $calendarRepository) - { - $this->calendarRepository = $calendarRepository; + public function __construct( + private readonly CalendarRepository $calendarRepository, + ) { } public function buildForm(FormBuilderInterface $builder) { // No form necessary } + public function getFormDefaultData(): array { return []; @@ -98,6 +97,9 @@ class CountCalendars implements ExportInterface, GroupedExportInterface $qb = $this->calendarRepository->createQueryBuilder('cal'); $qb->select('COUNT(cal.id) AS export_result'); + $qb->leftJoin('cal.accompanyingPeriod', 'acp'); + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); return $qb; } diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php index dcbfb695b..f7f19d1e4 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php @@ -13,29 +13,26 @@ namespace Chill\CalendarBundle\Export\Export; use Chill\CalendarBundle\Export\Declarations; use Chill\CalendarBundle\Repository\CalendarRepository; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface { - private CalendarRepository $calendarRepository; - - public function __construct( - CalendarRepository $calendarRepository - ) { - $this->calendarRepository = $calendarRepository; + public function __construct(private readonly CalendarRepository $calendarRepository) + { } public function buildForm(FormBuilderInterface $builder): void { // no form needed } + public function getFormDefaultData(): array { return []; @@ -59,7 +56,7 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { if ('export_result' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } $labels = array_combine($values, $values); @@ -92,8 +89,10 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface { $qb = $this->calendarRepository->createQueryBuilder('cal'); - $qb - ->select('AVG(cal.endDate - cal.startDate) AS export_result'); + $qb->select('AVG(cal.endDate - cal.startDate) AS export_result'); + $qb->join('cal.accompanyingPeriod', 'acp'); + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); return $qb; } diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php index 7f509a896..e9920444a 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php @@ -13,29 +13,26 @@ namespace Chill\CalendarBundle\Export\Export; use Chill\CalendarBundle\Export\Declarations; use Chill\CalendarBundle\Repository\CalendarRepository; +use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface { - private CalendarRepository $calendarRepository; - - public function __construct( - CalendarRepository $calendarRepository - ) { - $this->calendarRepository = $calendarRepository; + public function __construct(private readonly CalendarRepository $calendarRepository) + { } public function buildForm(FormBuilderInterface $builder): void { // no form needed } + public function getFormDefaultData(): array { return []; @@ -59,7 +56,7 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { if ('export_result' !== $key) { - throw new LogicException("the key {$key} is not used by this export"); + throw new \LogicException("the key {$key} is not used by this export"); } $labels = array_combine($values, $values); @@ -92,8 +89,10 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface { $qb = $this->calendarRepository->createQueryBuilder('cal'); - $qb - ->select('SUM(cal.endDate - cal.startDate) AS export_result'); + $qb->select('SUM(cal.endDate - cal.startDate) AS export_result'); + $qb->join('cal.accompanyingPeriod', 'acp'); + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); return $qb; } diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php index 0cef89c20..888ee4363 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php @@ -22,11 +22,8 @@ use Symfony\Component\Form\FormBuilderInterface; class AgentFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) + public function __construct(private readonly UserRender $userRender) { - $this->userRender = $userRender; } public function addRole(): ?string @@ -63,6 +60,7 @@ class AgentFilter implements FilterInterface 'expanded' => true, ]); } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php index 4260ecd99..1fe9eadc5 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php @@ -21,11 +21,8 @@ use Symfony\Component\Form\FormBuilderInterface; class BetweenDatesFilter implements FilterInterface { - private RollingDateConverterInterface $rollingDateConverter; - - public function __construct(RollingDateConverterInterface $rollingDateConverter) + public function __construct(private readonly RollingDateConverterInterface $rollingDateConverter) { - $this->rollingDateConverter = $rollingDateConverter; } public function addRole(): ?string @@ -63,6 +60,7 @@ class BetweenDatesFilter implements FilterInterface ->add('date_from', PickRollingDateType::class, []) ->add('date_to', PickRollingDateType::class, []); } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php index 5ffb7c01f..e493ce0bf 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php @@ -20,7 +20,6 @@ namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Export\FilterInterface; -use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; @@ -29,17 +28,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; class CalendarRangeFilter implements FilterInterface { private const CHOICES = [ - 'Not made within a calendar range' => true, - 'Made within a calendar range' => false, + 'Not made within a calendar range' => 'true', + 'Made within a calendar range' => 'false', ]; - private const DEFAULT_CHOICE = false; + private const DEFAULT_CHOICE = 'false'; - private TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) + public function __construct(private readonly TranslatorInterface $translator) { - $this->translator = $translator; } public function addRole(): ?string @@ -71,6 +67,7 @@ class CalendarRangeFilter implements FilterInterface 'empty_data' => self::DEFAULT_CHOICE, ]); } + public function getFormDefaultData(): array { return ['hasCalendarRange' => self::DEFAULT_CHOICE]; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php index d02bb8e9d..b2b5e6a12 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php @@ -12,28 +12,24 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Doctrine\ORM\Query\Expr\Andx; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; -use function in_array; -class JobFilter implements FilterInterface +final readonly class JobFilter implements FilterInterface { - protected TranslatorInterface $translator; - - private TranslatableStringHelper $translatableStringHelper; + private const PREFIX = 'cal_filter_job'; public function __construct( - TranslatorInterface $translator, - TranslatableStringHelper $translatableStringHelper + private TranslatableStringHelper $translatableStringHelper, + private UserJobRepositoryInterface $userJobRepository ) { - $this->translator = $translator; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -43,21 +39,31 @@ class JobFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('caluser', $qb->getAllAliases(), true)) { - $qb->join('cal.mainUser', 'caluser'); - } + $p = self::PREFIX; - $where = $qb->getDQLPart('where'); - $clause = $qb->expr()->in('caluser.userJob', ':job'); - - if ($where instanceof Andx) { - $where->add($clause); - } else { - $where = $qb->expr()->andX($clause); - } - - $qb->add('where', $where); - $qb->setParameter('job', $data['job']); + $qb + ->leftJoin('cal.mainUser', "{$p}_user") + ->leftJoin( + UserJobHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // job_at based on cal.startDate + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'cal.startDate'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'cal.startDate') + ) + ) + ) + ->andWhere($qb->expr()->in("{$p}_history.job", ":{$p}_job")) + ->setParameter( + "{$p}_job", + $data['job'] + ); } public function applyOn(): string @@ -67,18 +73,16 @@ class JobFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { - $builder->add('job', EntityType::class, [ - 'class' => UserJob::class, - 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize( - $j->getLabel() - ), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('job', EntityType::class, [ + 'class' => UserJob::class, + 'choices' => $this->userJobRepository->findAllActive(), + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize( + $j->getLabel() + ), + 'multiple' => true, + 'expanded' => true, + ]); } public function describeAction($data, $format = 'string'): array @@ -91,13 +95,20 @@ class JobFilter implements FilterInterface ); } - return ['Filtered by agent job: only %jobs%', [ + return ['export.filter.calendar.agent_job.Filtered by agent job: only %jobs%', [ '%jobs%' => implode(', ', $userJobs), ]]; } + public function getFormDefaultData(): array + { + return [ + 'job' => [], + ]; + } + public function getTitle(): string { - return 'Filter calendars by agent job'; + return 'export.filter.calendar.agent_job.Filter calendars by agent job'; } } diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php index d8a40da72..179258e41 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php @@ -13,27 +13,25 @@ namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Doctrine\ORM\Query\Expr\Andx; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Contracts\Translation\TranslatorInterface; -use function in_array; class ScopeFilter implements FilterInterface { - protected TranslatorInterface $translator; - - private TranslatableStringHelper $translatableStringHelper; + private const PREFIX = 'cal_filter_scope'; public function __construct( - TranslatorInterface $translator, - TranslatableStringHelper $translatableStringHelper + protected TranslatorInterface $translator, + private readonly TranslatableStringHelper $translatableStringHelper, + private readonly ScopeRepositoryInterface $scopeRepository ) { - $this->translator = $translator; - $this->translatableStringHelper = $translatableStringHelper; } public function addRole(): ?string @@ -43,45 +41,53 @@ class ScopeFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('caluser', $qb->getAllAliases(), true)) { - $qb->join('cal.mainUser', 'caluser'); - } + $p = self::PREFIX; - $where = $qb->getDQLPart('where'); - $clause = $qb->expr()->in('caluser.mainScope', ':scope'); - - if ($where instanceof Andx) { - $where->add($clause); - } else { - $where = $qb->expr()->andX($clause); - } - - $qb->add('where', $where); - $qb->setParameter('scope', $data['scope']); + $qb + ->leftJoin('cal.mainUser', "{$p}_user") + ->leftJoin( + UserScopeHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + // scope_at based on cal.startDate + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", 'cal.startDate'), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", 'cal.startDate') + ) + ) + ) + ->andWhere($qb->expr()->in("{$p}_history.scope", ":{$p}_scope")) + ->setParameter( + "{$p}_scope", + $data['scope'] + ); } - public function applyOn() + public function applyOn(): string { return Declarations::CALENDAR_TYPE; } public function buildForm(FormBuilderInterface $builder) { - $builder->add('scope', EntityType::class, [ - 'class' => Scope::class, - 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( - $s->getName() - ), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('scope', EntityType::class, [ + 'class' => Scope::class, + 'choices' => $this->scopeRepository->findAllActive(), + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( + $s->getName() + ), + 'multiple' => true, + 'expanded' => true, + ]); } - public function describeAction($data, $format = 'string') + public function describeAction($data, $format = 'string'): array { $scopes = []; @@ -91,13 +97,20 @@ class ScopeFilter implements FilterInterface ); } - return ['Filtered by agent scope: only %scopes%', [ + return ['export.filter.calendar.agent_scope.Filtered by agent scope: only %scopes%', [ '%scopes%' => implode(', ', $scopes), ]]; } - public function getTitle() + public function getFormDefaultData(): array { - return 'Filter calendars by agent scope'; + return [ + 'scope' => [], + ]; + } + + public function getTitle(): string + { + return 'export.filter.calendar.agent_scope.Filter calendars by agent scope'; } } diff --git a/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php b/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php index 34a501028..224a74f01 100644 --- a/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php +++ b/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Form; use Chill\CalendarBundle\Entity\CalendarDoc\CalendarDocEditDTO; -use Chill\DocStoreBundle\Form\StoredObjectType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; diff --git a/src/Bundle/ChillCalendarBundle/Form/CalendarType.php b/src/Bundle/ChillCalendarBundle/Form/CalendarType.php index a2d15484f..b83bb992d 100644 --- a/src/Bundle/ChillCalendarBundle/Form/CalendarType.php +++ b/src/Bundle/ChillCalendarBundle/Form/CalendarType.php @@ -21,7 +21,6 @@ use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\PrivateCommentType; use Chill\PersonBundle\Form\DataTransformer\PersonsToIdDataTransformer; use Chill\ThirdPartyBundle\Form\DataTransformer\ThirdPartiesToIdDataTransformer; -use DateTimeImmutable; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; @@ -32,32 +31,14 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class CalendarType extends AbstractType { - private IdToCalendarRangeDataTransformer $calendarRangeDataTransformer; - - private IdToLocationDataTransformer $idToLocationDataTransformer; - - private IdToUserDataTransformer $idToUserDataTransformer; - - private IdToUsersDataTransformer $idToUsersDataTransformer; - - private ThirdPartiesToIdDataTransformer $partiesToIdDataTransformer; - - private PersonsToIdDataTransformer $personsToIdDataTransformer; - public function __construct( - PersonsToIdDataTransformer $personsToIdDataTransformer, - IdToUserDataTransformer $idToUserDataTransformer, - IdToUsersDataTransformer $idToUsersDataTransformer, - IdToLocationDataTransformer $idToLocationDataTransformer, - ThirdPartiesToIdDataTransformer $partiesToIdDataTransformer, - IdToCalendarRangeDataTransformer $idToCalendarRangeDataTransformer + private readonly PersonsToIdDataTransformer $personsToIdDataTransformer, + private readonly IdToUserDataTransformer $idToUserDataTransformer, + private readonly IdToUsersDataTransformer $idToUsersDataTransformer, + private readonly IdToLocationDataTransformer $idToLocationDataTransformer, + private readonly ThirdPartiesToIdDataTransformer $partiesToIdDataTransformer, + private readonly IdToCalendarRangeDataTransformer $calendarRangeDataTransformer ) { - $this->personsToIdDataTransformer = $personsToIdDataTransformer; - $this->idToUserDataTransformer = $idToUserDataTransformer; - $this->idToUsersDataTransformer = $idToUsersDataTransformer; - $this->idToLocationDataTransformer = $idToLocationDataTransformer; - $this->partiesToIdDataTransformer = $partiesToIdDataTransformer; - $this->calendarRangeDataTransformer = $idToCalendarRangeDataTransformer; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -91,42 +72,42 @@ class CalendarType extends AbstractType $builder->add('startDate', HiddenType::class); $builder->get('startDate') ->addModelTransformer(new CallbackTransformer( - static function (?DateTimeImmutable $dateTimeImmutable): string { + static function (?\DateTimeImmutable $dateTimeImmutable): string { if (null !== $dateTimeImmutable) { - $res = date_format($dateTimeImmutable, DateTimeImmutable::ATOM); + $res = date_format($dateTimeImmutable, \DateTimeImmutable::ATOM); } else { $res = ''; } return $res; }, - static function (?string $dateAsString): ?DateTimeImmutable { + static function (?string $dateAsString): ?\DateTimeImmutable { if ('' === $dateAsString || null === $dateAsString) { return null; } - return DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $dateAsString); + return \DateTimeImmutable::createFromFormat(\DateTimeImmutable::ATOM, $dateAsString); } )); $builder->add('endDate', HiddenType::class); $builder->get('endDate') ->addModelTransformer(new CallbackTransformer( - static function (?DateTimeImmutable $dateTimeImmutable): string { + static function (?\DateTimeImmutable $dateTimeImmutable): string { if (null !== $dateTimeImmutable) { - $res = date_format($dateTimeImmutable, DateTimeImmutable::ATOM); + $res = date_format($dateTimeImmutable, \DateTimeImmutable::ATOM); } else { $res = ''; } return $res; }, - static function (?string $dateAsString): ?DateTimeImmutable { + static function (?string $dateAsString): ?\DateTimeImmutable { if ('' === $dateAsString || null === $dateAsString) { return null; } - return DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $dateAsString); + return \DateTimeImmutable::createFromFormat(\DateTimeImmutable::ATOM, $dateAsString); } )); diff --git a/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php index 4b9dca19f..3997c4dad 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -19,16 +19,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface { - protected TranslatorInterface $translator; - - private Security $security; - - public function __construct( - Security $security, - TranslatorInterface $translator - ) { - $this->security = $security; - $this->translator = $translator; + public function __construct(private readonly Security $security, protected TranslatorInterface $translator) + { } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php index eccbe1ffb..c7bbc756c 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php @@ -19,16 +19,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class PersonMenuBuilder implements LocalMenuBuilderInterface { - protected TranslatorInterface $translator; - - private Security $security; - - public function __construct( - Security $security, - TranslatorInterface $translator - ) { - $this->security = $security; - $this->translator = $translator; + public function __construct(private readonly Security $security, protected TranslatorInterface $translator) + { } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php index 525039910..90b94b08e 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php @@ -18,16 +18,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class UserMenuBuilder implements LocalMenuBuilderInterface { - public TranslatorInterface $translator; - - private Security $security; - - public function __construct( - Security $security, - TranslatorInterface $translator - ) { - $this->security = $security; - $this->translator = $translator; + public function __construct(private readonly Security $security, public TranslatorInterface $translator) + { } public function buildMenu($menuId, MenuItem $menu, array $parameters) diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php index f97cbc7b2..d0feca3d8 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php @@ -21,7 +21,6 @@ namespace Chill\CalendarBundle\Messenger\Doctrine; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Messenger\Message\CalendarMessage; use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage; -use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\PostPersistEventArgs; use Doctrine\ORM\Event\PostRemoveEventArgs; use Doctrine\ORM\Event\PostUpdateEventArgs; @@ -30,14 +29,8 @@ use Symfony\Component\Security\Core\Security; class CalendarEntityListener { - private MessageBusInterface $messageBus; - - private Security $security; - - public function __construct(MessageBusInterface $messageBus, Security $security) + public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) { - $this->messageBus = $messageBus; - $this->security = $security; } public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php index 4df548277..cc3bf649e 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php @@ -21,7 +21,6 @@ namespace Chill\CalendarBundle\Messenger\Doctrine; use Chill\CalendarBundle\Entity\CalendarRange; use Chill\CalendarBundle\Messenger\Message\CalendarRangeMessage; use Chill\CalendarBundle\Messenger\Message\CalendarRangeRemovedMessage; -use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\PostPersistEventArgs; use Doctrine\ORM\Event\PostRemoveEventArgs; use Doctrine\ORM\Event\PostUpdateEventArgs; @@ -30,14 +29,8 @@ use Symfony\Component\Security\Core\Security; class CalendarRangeEntityListener { - private MessageBusInterface $messageBus; - - private Security $security; - - public function __construct(MessageBusInterface $messageBus, Security $security) + public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) { - $this->messageBus = $messageBus; - $this->security = $security; } public function postPersist(CalendarRange $calendarRange, PostPersistEventArgs $eventArgs): void diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php index 4e3ae5891..c55bd8144 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php @@ -31,14 +31,8 @@ use Symfony\Component\Messenger\Handler\MessageHandlerInterface; */ class CalendarRangeRemoveToRemoteHandler implements MessageHandlerInterface { - private RemoteCalendarConnectorInterface $remoteCalendarConnector; - - private UserRepository $userRepository; - - public function __construct(RemoteCalendarConnectorInterface $remoteCalendarConnector, UserRepository $userRepository) + public function __construct(private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector, private readonly UserRepository $userRepository) { - $this->remoteCalendarConnector = $remoteCalendarConnector; - $this->userRepository = $userRepository; } public function __invoke(CalendarRangeRemovedMessage $calendarRangeRemovedMessage) diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php index 79bcd24ee..950ca526d 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php @@ -32,20 +32,8 @@ use Symfony\Component\Messenger\Handler\MessageHandlerInterface; */ class CalendarRangeToRemoteHandler implements MessageHandlerInterface { - private CalendarRangeRepository $calendarRangeRepository; - - private EntityManagerInterface $entityManager; - - private RemoteCalendarConnectorInterface $remoteCalendarConnector; - - public function __construct( - CalendarRangeRepository $calendarRangeRepository, - RemoteCalendarConnectorInterface $remoteCalendarConnector, - EntityManagerInterface $entityManager - ) { - $this->calendarRangeRepository = $calendarRangeRepository; - $this->remoteCalendarConnector = $remoteCalendarConnector; - $this->entityManager = $entityManager; + public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository, private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector, private readonly EntityManagerInterface $entityManager) + { } public function __invoke(CalendarRangeMessage $calendarRangeMessage): void diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php index f087766ec..6838d3147 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php @@ -31,17 +31,8 @@ use Symfony\Component\Messenger\Handler\MessageHandlerInterface; */ class CalendarRemoveHandler implements MessageHandlerInterface { - private CalendarRangeRepository $calendarRangeRepository; - - private RemoteCalendarConnectorInterface $remoteCalendarConnector; - - private UserRepositoryInterface $userRepository; - - public function __construct(RemoteCalendarConnectorInterface $remoteCalendarConnector, CalendarRangeRepository $calendarRangeRepository, UserRepositoryInterface $userRepository) + public function __construct(private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector, private readonly CalendarRangeRepository $calendarRangeRepository, private readonly UserRepositoryInterface $userRepository) { - $this->remoteCalendarConnector = $remoteCalendarConnector; - $this->calendarRangeRepository = $calendarRangeRepository; - $this->userRepository = $userRepository; } public function __invoke(CalendarRemovedMessage $message) diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php index eabdf9063..310e8734b 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php @@ -37,32 +37,8 @@ use Symfony\Component\Messenger\Handler\MessageHandlerInterface; */ class CalendarToRemoteHandler implements MessageHandlerInterface { - private RemoteCalendarConnectorInterface $calendarConnector; - - private CalendarRangeRepository $calendarRangeRepository; - - private CalendarRepository $calendarRepository; - - private EntityManagerInterface $entityManager; - - private InviteRepository $inviteRepository; - - private UserRepository $userRepository; - - public function __construct( - CalendarRangeRepository $calendarRangeRepository, - CalendarRepository $calendarRepository, - EntityManagerInterface $entityManager, - InviteRepository $inviteRepository, - RemoteCalendarConnectorInterface $calendarConnector, - UserRepository $userRepository - ) { - $this->calendarConnector = $calendarConnector; - $this->calendarRepository = $calendarRepository; - $this->calendarRangeRepository = $calendarRangeRepository; - $this->entityManager = $entityManager; - $this->userRepository = $userRepository; - $this->inviteRepository = $inviteRepository; + public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository, private readonly CalendarRepository $calendarRepository, private readonly EntityManagerInterface $entityManager, private readonly InviteRepository $inviteRepository, private readonly RemoteCalendarConnectorInterface $calendarConnector, private readonly UserRepository $userRepository) + { } public function __invoke(CalendarMessage $calendarMessage) diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php index e1df3ac3d..1d987c19e 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php @@ -31,17 +31,8 @@ use Symfony\Component\Messenger\Handler\MessageHandlerInterface; */ class InviteUpdateHandler implements MessageHandlerInterface { - private EntityManagerInterface $em; - - private InviteRepository $inviteRepository; - - private RemoteCalendarConnectorInterface $remoteCalendarConnector; - - public function __construct(EntityManagerInterface $em, InviteRepository $inviteRepository, RemoteCalendarConnectorInterface $remoteCalendarConnector) + public function __construct(private readonly EntityManagerInterface $em, private readonly InviteRepository $inviteRepository, private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector) { - $this->em = $em; - $this->inviteRepository = $inviteRepository; - $this->remoteCalendarConnector = $remoteCalendarConnector; } public function __invoke(InviteUpdateMessage $inviteUpdateMessage): void diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php index a09c70c1e..26908a6e4 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php @@ -36,40 +36,8 @@ use Symfony\Component\Messenger\Handler\MessageHandlerInterface; */ class MSGraphChangeNotificationHandler implements MessageHandlerInterface { - private CalendarRangeRepository $calendarRangeRepository; - - private CalendarRangeSyncer $calendarRangeSyncer; - - private CalendarRepository $calendarRepository; - - private CalendarSyncer $calendarSyncer; - - private EntityManagerInterface $em; - - private LoggerInterface $logger; - - private MapCalendarToUser $mapCalendarToUser; - - private UserRepository $userRepository; - - public function __construct( - CalendarRangeRepository $calendarRangeRepository, - CalendarRangeSyncer $calendarRangeSyncer, - CalendarRepository $calendarRepository, - CalendarSyncer $calendarSyncer, - EntityManagerInterface $em, - LoggerInterface $logger, - MapCalendarToUser $mapCalendarToUser, - UserRepository $userRepository - ) { - $this->calendarRangeRepository = $calendarRangeRepository; - $this->calendarRangeSyncer = $calendarRangeSyncer; - $this->calendarRepository = $calendarRepository; - $this->calendarSyncer = $calendarSyncer; - $this->em = $em; - $this->logger = $logger; - $this->mapCalendarToUser = $mapCalendarToUser; - $this->userRepository = $userRepository; + public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository, private readonly CalendarRangeSyncer $calendarRangeSyncer, private readonly CalendarRepository $calendarRepository, private readonly CalendarSyncer $calendarSyncer, private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly MapCalendarToUser $mapCalendarToUser, private readonly UserRepository $userRepository) + { } public function __invoke(MSGraphChangeNotificationMessage $changeNotificationMessage): void @@ -77,7 +45,7 @@ class MSGraphChangeNotificationHandler implements MessageHandlerInterface $user = $this->userRepository->find($changeNotificationMessage->getUserId()); if (null === $user) { - $this->logger->warning(self::class . ' notification concern non-existent user, skipping'); + $this->logger->warning(self::class.' notification concern non-existent user, skipping'); return; } @@ -86,7 +54,7 @@ class MSGraphChangeNotificationHandler implements MessageHandlerInterface $secret = $this->mapCalendarToUser->getSubscriptionSecret($user); if ($secret !== ($notification['clientState'] ?? -1)) { - $this->logger->warning(self::class . ' could not validate secret, skipping'); + $this->logger->warning(self::class.' could not validate secret, skipping'); continue; } @@ -101,7 +69,7 @@ class MSGraphChangeNotificationHandler implements MessageHandlerInterface $this->calendarSyncer->handleCalendarSync($calendar, $notification, $user); $this->em->flush(); } else { - $this->logger->info(self::class . ' id not found in any calendar nor calendar range'); + $this->logger->info(self::class.' id not found in any calendar nor calendar range'); } } diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php index c8a8f8aeb..9bcd81c67 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php @@ -24,15 +24,13 @@ use Chill\MainBundle\Entity\User; class CalendarMessage { - public const CALENDAR_PERSIST = 'CHILL_CALENDAR_CALENDAR_PERSIST'; + final public const CALENDAR_PERSIST = 'CHILL_CALENDAR_CALENDAR_PERSIST'; - public const CALENDAR_UPDATE = 'CHILL_CALENDAR_CALENDAR_UPDATE'; + final public const CALENDAR_UPDATE = 'CHILL_CALENDAR_CALENDAR_UPDATE'; - private string $action; + private readonly int $byUserId; - private int $byUserId; - - private int $calendarId; + private readonly int $calendarId; private array $newInvitesIds = []; @@ -47,12 +45,11 @@ class CalendarMessage public function __construct( Calendar $calendar, - string $action, + private readonly string $action, User $byUser ) { $this->calendarId = $calendar->getId(); $this->byUserId = $byUser->getId(); - $this->action = $action; $this->previousCalendarRangeId = null !== $calendar->previousCalendarRange ? $calendar->previousCalendarRange->getId() : null; $this->previousMainUserId = null !== $calendar->previousMainUser ? diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php index 526826ff8..13669a0a2 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php @@ -23,19 +23,16 @@ use Chill\MainBundle\Entity\User; class CalendarRangeMessage { - public const CALENDAR_RANGE_PERSIST = 'CHILL_CALENDAR_CALENDAR_RANGE_PERSIST'; + final public const CALENDAR_RANGE_PERSIST = 'CHILL_CALENDAR_CALENDAR_RANGE_PERSIST'; - public const CALENDAR_RANGE_UPDATE = 'CHILL_CALENDAR_CALENDAR_RANGE_UPDATE'; - - private string $action; + final public const CALENDAR_RANGE_UPDATE = 'CHILL_CALENDAR_CALENDAR_RANGE_UPDATE'; private ?int $byUserId = null; - private int $calendarRangeId; + private readonly int $calendarRangeId; - public function __construct(CalendarRange $calendarRange, string $action, ?User $byUser) + public function __construct(CalendarRange $calendarRange, private readonly string $action, ?User $byUser) { - $this->action = $action; $this->calendarRangeId = $calendarRange->getId(); if (null !== $byUser) { diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeRemovedMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeRemovedMessage.php index 783484592..eb8be6838 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeRemovedMessage.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeRemovedMessage.php @@ -25,11 +25,11 @@ class CalendarRangeRemovedMessage { private ?int $byUserId = null; - private int $calendarRangeUserId; + private readonly int $calendarRangeUserId; - private array $remoteAttributes; + private readonly array $remoteAttributes; - private string $remoteId; + private readonly string $remoteId; public function __construct(CalendarRange $calendarRange, ?User $byUser) { diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php index 65831ebe0..53dcea28c 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php @@ -27,11 +27,11 @@ class CalendarRemovedMessage private ?int $byUserId = null; - private int $calendarUserId; + private readonly int $calendarUserId; - private array $remoteAttributes; + private readonly array $remoteAttributes; - private string $remoteId; + private readonly string $remoteId; public function __construct(Calendar $calendar, ?User $byUser) { diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php index 35f78fc7d..d18ab8db1 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php @@ -23,9 +23,9 @@ use Chill\MainBundle\Entity\User; class InviteUpdateMessage { - private int $byUserId; + private readonly int $byUserId; - private int $inviteId; + private readonly int $inviteId; public function __construct(Invite $invite, User $byUser) { diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php index f1d3b6b04..682369e03 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php @@ -20,14 +20,8 @@ namespace Chill\CalendarBundle\Messenger\Message; class MSGraphChangeNotificationMessage { - private array $content; - - private int $userId; - - public function __construct(array $content, int $userId) + public function __construct(private readonly array $content, private readonly int $userId) { - $this->content = $content; - $this->userId = $userId; } public function getContent(): array diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php index 8b86ba0ec..2764a46e3 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php @@ -24,14 +24,8 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface; class AddressConverter { - private AddressRender $addressRender; - - private TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct(AddressRender $addressRender, TranslatableStringHelperInterface $translatableStringHelper) + public function __construct(private readonly AddressRender $addressRender, private readonly TranslatableStringHelperInterface $translatableStringHelper) { - $this->addressRender = $addressRender; - $this->translatableStringHelper = $translatableStringHelper; } public function addressToRemote(Address $address): array diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php index c400b6694..f3d764acc 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php @@ -19,8 +19,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph; use Chill\MainBundle\Entity\User; -use DateTimeImmutable; -use LogicException; use Psr\Log\LoggerInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; @@ -30,39 +28,23 @@ use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; */ class EventsOnUserSubscriptionCreator { - private LoggerInterface $logger; - - private MachineHttpClient $machineHttpClient; - - private MapCalendarToUser $mapCalendarToUser; - - private UrlGeneratorInterface $urlGenerator; - - public function __construct( - LoggerInterface $logger, - MachineHttpClient $machineHttpClient, - MapCalendarToUser $mapCalendarToUser, - UrlGeneratorInterface $urlGenerator - ) { - $this->logger = $logger; - $this->machineHttpClient = $machineHttpClient; - $this->mapCalendarToUser = $mapCalendarToUser; - $this->urlGenerator = $urlGenerator; + public function __construct(private readonly LoggerInterface $logger, private readonly MachineHttpClient $machineHttpClient, private readonly MapCalendarToUser $mapCalendarToUser, private readonly UrlGeneratorInterface $urlGenerator) + { } /** + * @return array{secret: string, id: string, expiration: int} + * * @throws ClientExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface - * - * @return array */ - public function createSubscriptionForUser(User $user, DateTimeImmutable $expiration): array + public function createSubscriptionForUser(User $user, \DateTimeImmutable $expiration): array { if (null === $userId = $this->mapCalendarToUser->getUserId($user)) { - throw new LogicException('no user id'); + throw new \LogicException('no user id'); } $subscription = [ @@ -74,7 +56,7 @@ class EventsOnUserSubscriptionCreator ), 'resource' => "/users/{$userId}/calendar/events", 'clientState' => $secret = base64_encode(openssl_random_pseudo_bytes(92, $cstrong)), - 'expirationDateTime' => $expiration->format(DateTimeImmutable::ATOM), + 'expirationDateTime' => $expiration->format(\DateTimeImmutable::ATOM), ]; try { @@ -97,26 +79,26 @@ class EventsOnUserSubscriptionCreator } /** + * @return array{secret: string, id: string, expiration: int} + * * @throws ClientExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface - * - * @return array */ - public function renewSubscriptionForUser(User $user, DateTimeImmutable $expiration): array + public function renewSubscriptionForUser(User $user, \DateTimeImmutable $expiration): array { if (null === $userId = $this->mapCalendarToUser->getUserId($user)) { - throw new LogicException('no user id'); + throw new \LogicException('no user id'); } if (null === $subscriptionId = $this->mapCalendarToUser->getActiveSubscriptionId($user)) { - throw new LogicException('no user id'); + throw new \LogicException('no user id'); } $subscription = [ - 'expirationDateTime' => $expiration->format(DateTimeImmutable::ATOM), + 'expirationDateTime' => $expiration->format(\DateTimeImmutable::ATOM), ]; try { diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php index 396dfd931..cbf97806e 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php @@ -22,11 +22,8 @@ use Chill\MainBundle\Entity\Location; class LocationConverter { - private AddressConverter $addressConverter; - - public function __construct(AddressConverter $addressConverter) + public function __construct(private readonly AddressConverter $addressConverter) { - $this->addressConverter = $addressConverter; } public function locationToRemote(Location $location): array diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php index c70072a47..22dbaa2da 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php @@ -13,7 +13,6 @@ namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph; use Chill\CalendarBundle\Exception\UserAbsenceSyncException; use Chill\MainBundle\Entity\User; -use Psr\Log\LoggerInterface; use Symfony\Component\Clock\ClockInterface; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; @@ -34,7 +33,7 @@ final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface /** * @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft */ - public function isUserAbsent(User $user): bool|null + public function isUserAbsent(User $user): ?bool { $id = $this->mapCalendarToUser->getUserId($user); @@ -44,26 +43,24 @@ final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface try { $automaticRepliesSettings = $this->machineHttpClient - ->request('GET', 'users/' . $id . '/mailboxSettings/automaticRepliesSetting') + ->request('GET', 'users/'.$id.'/mailboxSettings/automaticRepliesSetting') ->toArray(true); } catch (ClientExceptionInterface|DecodingExceptionInterface|RedirectionExceptionInterface|TransportExceptionInterface $e) { - throw new UserAbsenceSyncException("Error receiving response for mailboxSettings", 0, $e); + throw new UserAbsenceSyncException('Error receiving response for mailboxSettings', 0, $e); } catch (ServerExceptionInterface $e) { - throw new UserAbsenceSyncException("Server error receiving response for mailboxSettings", 0, $e); + throw new UserAbsenceSyncException('Server error receiving response for mailboxSettings', 0, $e); } - if (!array_key_exists("status", $automaticRepliesSettings)) { - throw new \LogicException("no key \"status\" on automatic replies settings: " . json_encode($automaticRepliesSettings, JSON_THROW_ON_ERROR)); + if (!array_key_exists('status', $automaticRepliesSettings)) { + throw new \LogicException('no key "status" on automatic replies settings: '.json_encode($automaticRepliesSettings, JSON_THROW_ON_ERROR)); } return match ($automaticRepliesSettings['status']) { 'disabled' => false, 'alwaysEnabled' => true, - 'scheduled' => - RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledStartDateTime']['dateTime']) < $this->clock->now() + 'scheduled' => RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledStartDateTime']['dateTime']) < $this->clock->now() && RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledEndDateTime']['dateTime']) > $this->clock->now(), - default => throw new UserAbsenceSyncException("this status is not documented by Microsoft") + default => throw new UserAbsenceSyncException('this status is not documented by Microsoft') }; } - } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderInterface.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderInterface.php index a918bb7ea..7c3fd69d6 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderInterface.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderInterface.php @@ -18,5 +18,5 @@ interface MSUserAbsenceReaderInterface /** * @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft */ - public function isUserAbsent(User $user): bool|null; + public function isUserAbsent(User $user): ?bool; } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSync.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSync.php index 10bf21b9b..1d7b181f3 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSync.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSync.php @@ -37,13 +37,13 @@ readonly class MSUserAbsenceSync return; } - $this->logger->info("will change user absence", ['userId' => $user->getId()]); + $this->logger->info('will change user absence', ['userId' => $user->getId()]); if ($absence) { - $this->logger->debug("make user absent", ['userId' => $user->getId()]); + $this->logger->debug('make user absent', ['userId' => $user->getId()]); $user->setAbsenceStart($this->clock->now()); } else { - $this->logger->debug("make user present", ['userId' => $user->getId()]); + $this->logger->debug('make user present', ['userId' => $user->getId()]); $user->setAbsenceStart(null); } } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineHttpClient.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineHttpClient.php index cc1692fb7..f84d04120 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineHttpClient.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineHttpClient.php @@ -19,7 +19,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph; use League\OAuth2\Client\Tool\BearerAuthorizationTrait; -use LogicException; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; use Symfony\Contracts\HttpClient\ResponseStreamInterface; @@ -28,22 +27,16 @@ class MachineHttpClient implements HttpClientInterface { use BearerAuthorizationTrait; - private HttpClientInterface $decoratedClient; + private readonly HttpClientInterface $decoratedClient; - private MachineTokenStorage $machineTokenStorage; - - /** - * @param HttpClientInterface $decoratedClient - */ - public function __construct(MachineTokenStorage $machineTokenStorage, ?HttpClientInterface $decoratedClient = null) + public function __construct(private readonly MachineTokenStorage $machineTokenStorage, ?HttpClientInterface $decoratedClient = null) { $this->decoratedClient = $decoratedClient ?? \Symfony\Component\HttpClient\HttpClient::create(); - $this->machineTokenStorage = $machineTokenStorage; } /** * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface - * @throws LogicException if method is not supported + * @throws \LogicException if method is not supported */ public function request(string $method, string $url, array $options = []): ResponseInterface { @@ -69,7 +62,7 @@ class MachineHttpClient implements HttpClientInterface break; default: - throw new LogicException("Method not supported: {$method}"); + throw new \LogicException("Method not supported: {$method}"); } return $this->decoratedClient->request($method, $url, $options); diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php index ac62d44ac..f5d25caaf 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php @@ -29,14 +29,8 @@ class MachineTokenStorage private ?AccessTokenInterface $accessToken = null; - private Azure $azure; - - private ChillRedis $chillRedis; - - public function __construct(Azure $azure, ChillRedis $chillRedis) + public function __construct(private readonly Azure $azure, private readonly ChillRedis $chillRedis) { - $this->azure = $azure; - $this->chillRedis = $chillRedis; } public function getToken(): AccessTokenInterface diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php index 563bd3a38..aa3b1c4a4 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php @@ -19,46 +19,35 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph; use Chill\MainBundle\Entity\User; -use DateTimeImmutable; -use LogicException; use Psr\Log\LoggerInterface; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; -use function array_key_exists; /** * Write metadata to user, which allow to find his default calendar. */ class MapCalendarToUser { - public const EXPIRATION_SUBSCRIPTION_EVENT = 'subscription_events_expiration'; + final public const EXPIRATION_SUBSCRIPTION_EVENT = 'subscription_events_expiration'; - public const ID_SUBSCRIPTION_EVENT = 'subscription_events_id'; + final public const ID_SUBSCRIPTION_EVENT = 'subscription_events_id'; - public const METADATA_KEY = 'msgraph'; + final public const METADATA_KEY = 'msgraph'; - public const SECRET_SUBSCRIPTION_EVENT = 'subscription_events_secret'; + final public const SECRET_SUBSCRIPTION_EVENT = 'subscription_events_secret'; - private LoggerInterface $logger; - - private HttpClientInterface $machineHttpClient; - - public function __construct( - HttpClientInterface $machineHttpClient, - LoggerInterface $logger - ) { - $this->machineHttpClient = $machineHttpClient; - $this->logger = $logger; + public function __construct(private readonly HttpClientInterface $machineHttpClient, private readonly LoggerInterface $logger) + { } public function getActiveSubscriptionId(User $user): string { - if (!array_key_exists(self::METADATA_KEY, $user->getAttributes())) { - throw new LogicException('do not contains msgraph metadata'); + if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) { + throw new \LogicException('do not contains msgraph metadata'); } - if (!array_key_exists(self::ID_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) { - throw new LogicException('do not contains metadata for subscription id'); + if (!\array_key_exists(self::ID_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) { + throw new \LogicException('do not contains metadata for subscription id'); } return $user->getAttributes()[self::METADATA_KEY][self::ID_SUBSCRIPTION_EVENT]; @@ -93,12 +82,12 @@ class MapCalendarToUser public function getSubscriptionSecret(User $user): string { - if (!array_key_exists(self::METADATA_KEY, $user->getAttributes())) { - throw new LogicException('do not contains msgraph metadata'); + if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) { + throw new \LogicException('do not contains msgraph metadata'); } - if (!array_key_exists(self::SECRET_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) { - throw new LogicException('do not contains secret in msgraph'); + if (!\array_key_exists(self::SECRET_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) { + throw new \LogicException('do not contains secret in msgraph'); } return $user->getAttributes()[self::METADATA_KEY][self::SECRET_SUBSCRIPTION_EVENT]; @@ -124,25 +113,25 @@ class MapCalendarToUser public function hasActiveSubscription(User $user): bool { - if (!array_key_exists(self::METADATA_KEY, $user->getAttributes())) { + if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) { return false; } - if (!array_key_exists(self::EXPIRATION_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) { + if (!\array_key_exists(self::EXPIRATION_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) { return false; } return $user->getAttributes()[self::METADATA_KEY][self::EXPIRATION_SUBSCRIPTION_EVENT] - >= (new DateTimeImmutable('now'))->getTimestamp(); + >= (new \DateTimeImmutable('now'))->getTimestamp(); } public function hasSubscriptionSecret(User $user): bool { - if (!array_key_exists(self::METADATA_KEY, $user->getAttributes())) { + if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) { return false; } - return array_key_exists(self::SECRET_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY]); + return \array_key_exists(self::SECRET_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY]); } public function hasUserId(User $user): bool @@ -151,11 +140,11 @@ class MapCalendarToUser return false; } - if (!array_key_exists(self::METADATA_KEY, $user->getAttributes())) { + if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) { return false; } - return array_key_exists('id', $user->getAttributes()[self::METADATA_KEY]); + return \array_key_exists('id', $user->getAttributes()[self::METADATA_KEY]); } public function writeMetadata(User $user): User diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php index 9777bf4c0..02c9ea806 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php @@ -19,7 +19,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph; use League\OAuth2\Client\Tool\BearerAuthorizationTrait; -use LogicException; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; use Symfony\Contracts\HttpClient\ResponseStreamInterface; @@ -28,17 +27,11 @@ class OnBehalfOfUserHttpClient { use BearerAuthorizationTrait; - private HttpClientInterface $decoratedClient; + private readonly HttpClientInterface $decoratedClient; - private OnBehalfOfUserTokenStorage $tokenStorage; - - /** - * @param HttpClientInterface $decoratedClient - */ - public function __construct(OnBehalfOfUserTokenStorage $tokenStorage, ?HttpClientInterface $decoratedClient = null) + public function __construct(private readonly OnBehalfOfUserTokenStorage $tokenStorage, ?HttpClientInterface $decoratedClient = null) { $this->decoratedClient = $decoratedClient ?? \Symfony\Component\HttpClient\HttpClient::create(); - $this->tokenStorage = $tokenStorage; } public function request(string $method, string $url, array $options = []): ResponseInterface @@ -64,7 +57,7 @@ class OnBehalfOfUserHttpClient break; default: - throw new LogicException("Method not supported: {$method}"); + throw new \LogicException("Method not supported: {$method}"); } return $this->decoratedClient->request($method, $url, $options); diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php index 28f68e0e9..cc5b72b42 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph; -use LogicException; use Symfony\Component\HttpFoundation\Session\SessionInterface; use TheNetworg\OAuth2\Client\Provider\Azure; use TheNetworg\OAuth2\Client\Token\AccessToken; @@ -28,16 +27,10 @@ use TheNetworg\OAuth2\Client\Token\AccessToken; */ class OnBehalfOfUserTokenStorage { - public const MS_GRAPH_ACCESS_TOKEN = 'msgraph_access_token'; + final public const MS_GRAPH_ACCESS_TOKEN = 'msgraph_access_token'; - private Azure $azure; - - private SessionInterface $session; - - public function __construct(Azure $azure, SessionInterface $session) + public function __construct(private readonly Azure $azure, private readonly SessionInterface $session) { - $this->azure = $azure; - $this->session = $session; } public function getToken(): AccessToken @@ -46,7 +39,7 @@ class OnBehalfOfUserTokenStorage $token = $this->session->get(self::MS_GRAPH_ACCESS_TOKEN, null); if (null === $token) { - throw new LogicException('unexisting token'); + throw new \LogicException('unexisting token'); } if ($token->hasExpired()) { diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php index 176dbe0b0..94b488ddd 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php @@ -24,11 +24,7 @@ use Chill\CalendarBundle\Entity\Invite; use Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Templating\Entity\PersonRenderInterface; -use DateTimeImmutable; -use DateTimeZone; use Psr\Log\LoggerInterface; -use RuntimeException; -use Symfony\Component\Templating\EngineInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -41,44 +37,29 @@ class RemoteEventConverter * valid when the remote string contains also a timezone, like in * lastModifiedDate. */ - public const REMOTE_DATETIMEZONE_FORMAT = 'Y-m-d\\TH:i:s.u?P'; + final public const REMOTE_DATETIMEZONE_FORMAT = 'Y-m-d\\TH:i:s.u?P'; /** * Same as above, but sometimes the date is expressed with only 6 milliseconds. */ - public const REMOTE_DATETIMEZONE_FORMAT_ALT = 'Y-m-d\\TH:i:s.uP'; + final public const REMOTE_DATETIMEZONE_FORMAT_ALT = 'Y-m-d\\TH:i:s.uP'; private const REMOTE_DATE_FORMAT = 'Y-m-d\TH:i:s.u0'; private const REMOTE_DATETIME_WITHOUT_TZ_FORMAT = 'Y-m-d\TH:i:s.u?'; - private DateTimeZone $defaultDateTimeZone; + private readonly \DateTimeZone $defaultDateTimeZone; - private EngineInterface $engine; - - private LocationConverter $locationConverter; - - private LoggerInterface $logger; - - private PersonRenderInterface $personRender; - - private DateTimeZone $remoteDateTimeZone; - - private TranslatorInterface $translator; + private readonly \DateTimeZone $remoteDateTimeZone; public function __construct( - EngineInterface $engine, - LocationConverter $locationConverter, - LoggerInterface $logger, - PersonRenderInterface $personRender, - TranslatorInterface $translator + private readonly \Twig\Environment $engine, + private readonly LocationConverter $locationConverter, + private readonly LoggerInterface $logger, + private readonly PersonRenderInterface $personRender, + private readonly TranslatorInterface $translator ) { - $this->engine = $engine; - $this->locationConverter = $locationConverter; - $this->logger = $logger; - $this->translator = $translator; - $this->personRender = $personRender; - $this->defaultDateTimeZone = (new DateTimeImmutable())->getTimezone(); + $this->defaultDateTimeZone = (new \DateTimeImmutable())->getTimezone(); $this->remoteDateTimeZone = self::getRemoteTimeZone(); } @@ -118,7 +99,7 @@ class RemoteEventConverter { $result = array_merge( [ - 'subject' => '[Chill] ' . + 'subject' => '[Chill] '. implode( ', ', $calendar->getPersons()->map(fn (Person $p) => $this->personRender->renderString($p, []))->toArray() @@ -134,7 +115,7 @@ class RemoteEventConverter 'timeZone' => 'UTC', ], 'allowNewTimeProposals' => false, - 'transactionId' => 'calendar_' . $calendar->getId(), + 'transactionId' => 'calendar_'.$calendar->getId(), 'body' => [ 'contentType' => 'text', 'content' => $this->engine->render( @@ -167,45 +148,45 @@ class RemoteEventConverter public function convertAvailabilityToRemoteEvent(array $event): RemoteEvent { $startDate = - DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['start']['dateTime'], $this->remoteDateTimeZone) + \DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['start']['dateTime'], $this->remoteDateTimeZone) ->setTimezone($this->defaultDateTimeZone); $endDate = - DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['end']['dateTime'], $this->remoteDateTimeZone) + \DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['end']['dateTime'], $this->remoteDateTimeZone) ->setTimezone($this->defaultDateTimeZone); return new RemoteEvent( uniqid('generated_'), - $this->translator->trans('remote_ms_graph.freebusy_statuses.' . $event['status']), + $this->translator->trans('remote_ms_graph.freebusy_statuses.'.$event['status']), '', $startDate, $endDate ); } - public static function convertStringDateWithoutTimezone(string $date): DateTimeImmutable + public static function convertStringDateWithoutTimezone(string $date): \DateTimeImmutable { - $d = DateTimeImmutable::createFromFormat( + $d = \DateTimeImmutable::createFromFormat( self::REMOTE_DATETIME_WITHOUT_TZ_FORMAT, $date, self::getRemoteTimeZone() ); if (false === $d) { - throw new RuntimeException("could not convert string date to datetime: {$date}"); + throw new \RuntimeException("could not convert string date to datetime: {$date}"); } - return $d->setTimezone((new DateTimeImmutable())->getTimezone()); + return $d->setTimezone((new \DateTimeImmutable())->getTimezone()); } - public static function convertStringDateWithTimezone(string $date): DateTimeImmutable + public static function convertStringDateWithTimezone(string $date): \DateTimeImmutable { - $d = DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $date); + $d = \DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $date); if (false === $d) { - throw new RuntimeException("could not convert string date to datetime: {$date}"); + throw new \RuntimeException("could not convert string date to datetime: {$date}"); } - $d->setTimezone((new DateTimeImmutable())->getTimezone()); + $d->setTimezone((new \DateTimeImmutable())->getTimezone()); return $d; } @@ -213,10 +194,10 @@ class RemoteEventConverter public function convertToRemote(array $event): RemoteEvent { $startDate = - DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['start']['dateTime'], $this->remoteDateTimeZone) + \DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['start']['dateTime'], $this->remoteDateTimeZone) ->setTimezone($this->defaultDateTimeZone); $endDate = - DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['end']['dateTime'], $this->remoteDateTimeZone) + \DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['end']['dateTime'], $this->remoteDateTimeZone) ->setTimezone($this->defaultDateTimeZone); return new RemoteEvent( @@ -229,26 +210,22 @@ class RemoteEventConverter ); } - public function getLastModifiedDate(array $event): DateTimeImmutable + public function getLastModifiedDate(array $event): \DateTimeImmutable { - $date = DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $event['lastModifiedDateTime']); + $date = \DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $event['lastModifiedDateTime']); if (false === $date) { - $date = DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT_ALT, $event['lastModifiedDateTime']); + $date = \DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT_ALT, $event['lastModifiedDateTime']); } if (false === $date) { - $this->logger->error(self::class . ' Could not convert lastModifiedDate', [ + $this->logger->error(self::class.' Could not convert lastModifiedDate', [ 'actual' => $event['lastModifiedDateTime'], 'format' => self::REMOTE_DATETIMEZONE_FORMAT, 'format_alt' => self::REMOTE_DATETIMEZONE_FORMAT_ALT, ]); - throw new RuntimeException(sprintf( - 'could not convert lastModifiedDate: %s, expected format: %s', - $event['lastModifiedDateTime'], - self::REMOTE_DATETIMEZONE_FORMAT . ' and ' . self::REMOTE_DATETIMEZONE_FORMAT_ALT - )); + throw new \RuntimeException(sprintf('could not convert lastModifiedDate: %s, expected format: %s', $event['lastModifiedDateTime'], self::REMOTE_DATETIMEZONE_FORMAT.' and '.self::REMOTE_DATETIMEZONE_FORMAT_ALT)); } return $date; @@ -262,9 +239,9 @@ class RemoteEventConverter return 'Y-m-d\TH:i:s'; } - public static function getRemoteTimeZone(): DateTimeZone + public static function getRemoteTimeZone(): \DateTimeZone { - return new DateTimeZone('UTC'); + return new \DateTimeZone('UTC'); } private function buildInviteToAttendee(Invite $invite): array diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php index d05adfed7..0c9621aeb 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php @@ -24,29 +24,16 @@ use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteEventConverter; use Chill\MainBundle\Entity\User; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; -use RuntimeException; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; class CalendarRangeSyncer { - private EntityManagerInterface $em; - - private LoggerInterface $logger; - - private HttpClientInterface $machineHttpClient; - /** * @param MachineHttpClient $machineHttpClient */ - public function __construct( - EntityManagerInterface $em, - LoggerInterface $logger, - HttpClientInterface $machineHttpClient - ) { - $this->em = $em; - $this->logger = $logger; - $this->machineHttpClient = $machineHttpClient; + public function __construct(private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly HttpClientInterface $machineHttpClient) + { } public function handleCalendarRangeSync(CalendarRange $calendarRange, array $notification, User $user): void @@ -59,7 +46,7 @@ class CalendarRangeSyncer } $calendarRange->preventEnqueueChanges = true; - $this->logger->info(self::class . ' remove a calendar range because deleted on remote calendar'); + $this->logger->info(self::class.' remove a calendar range because deleted on remote calendar'); $this->em->remove($calendarRange); break; @@ -71,7 +58,7 @@ class CalendarRangeSyncer $notification['resource'] )->toArray(); } catch (ClientExceptionInterface $clientException) { - $this->logger->warning(self::class . ' could not retrieve event from ms graph. Already deleted ?', [ + $this->logger->warning(self::class.' could not retrieve event from ms graph. Already deleted ?', [ 'calendarRangeId' => $calendarRange->getId(), 'remoteEventId' => $notification['resource'], ]); @@ -82,7 +69,7 @@ class CalendarRangeSyncer $lastModified = RemoteEventConverter::convertStringDateWithTimezone($new['lastModifiedDateTime']); if ($calendarRange->getRemoteAttributes()['lastModifiedDateTime'] === $lastModified->getTimestamp()) { - $this->logger->info(self::class . ' change key is equals. Source is probably a local update', [ + $this->logger->info(self::class.' change key is equals. Source is probably a local update', [ 'calendarRangeId' => $calendarRange->getId(), 'remoteEventId' => $notification['resource'], ]); @@ -104,7 +91,7 @@ class CalendarRangeSyncer break; default: - throw new RuntimeException('This changeType is not suppored: ' . $notification['changeType']); + throw new \RuntimeException('This changeType is not suppored: '.$notification['changeType']); } } } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarSyncer.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarSyncer.php index b3febf4d4..06e0f2b39 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarSyncer.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarSyncer.php @@ -23,44 +23,23 @@ use Chill\CalendarBundle\Entity\Invite; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteEventConverter; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\UserRepositoryInterface; -use LogicException; use Psr\Log\LoggerInterface; -use RuntimeException; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; -use function in_array; class CalendarSyncer { - private LoggerInterface $logger; - - private HttpClientInterface $machineHttpClient; - - private UserRepositoryInterface $userRepository; - - public function __construct(LoggerInterface $logger, HttpClientInterface $machineHttpClient, UserRepositoryInterface $userRepository) + public function __construct(private readonly LoggerInterface $logger, private readonly HttpClientInterface $machineHttpClient, private readonly UserRepositoryInterface $userRepository) { - $this->logger = $logger; - $this->machineHttpClient = $machineHttpClient; - $this->userRepository = $userRepository; } public function handleCalendarSync(Calendar $calendar, array $notification, User $user): void { - switch ($notification['changeType']) { - case 'deleted': - $this->handleDeleteCalendar($calendar, $notification, $user); - - break; - - case 'updated': - $this->handleUpdateCalendar($calendar, $notification, $user); - - break; - - default: - throw new RuntimeException('this change type is not supported: ' . $notification['changeType']); - } + match ($notification['changeType']) { + 'deleted' => $this->handleDeleteCalendar($calendar, $notification, $user), + 'updated' => $this->handleUpdateCalendar($calendar, $notification, $user), + default => throw new \RuntimeException('this change type is not supported: '.$notification['changeType']), + }; } private function handleDeleteCalendar(Calendar $calendar, array $notification, User $user): void @@ -79,7 +58,7 @@ class CalendarSyncer $notification['resource'] )->toArray(); } catch (ClientExceptionInterface $clientException) { - $this->logger->warning(self::class . ' could not retrieve event from ms graph. Already deleted ?', [ + $this->logger->warning(self::class.' could not retrieve event from ms graph. Already deleted ?', [ 'calendarId' => $calendar->getId(), 'remoteEventId' => $notification['resource'], ]); @@ -96,7 +75,7 @@ class CalendarSyncer ); if ($calendar->getRemoteAttributes()['lastModifiedDateTime'] === $lastModified->getTimestamp()) { - $this->logger->info(self::class . ' change key is equals. Source is probably a local update', [ + $this->logger->info(self::class.' change key is equals. Source is probably a local update', [ 'calendarRangeId' => $calendar->getId(), 'remoteEventId' => $notification['resource'], ]); @@ -137,7 +116,7 @@ class CalendarSyncer } $email = $attendee['emailAddress']['address']; - $emails[] = strtolower($email); + $emails[] = strtolower((string) $email); $user = $this->userRepository->findOneByUsernameOrEmail($email); if (null === $user) { @@ -150,38 +129,17 @@ class CalendarSyncer $invite = $calendar->getInviteForUser($user); - switch ($status) { - // possible cases: none, organizer, tentativelyAccepted, accepted, declined, notResponded. - case 'none': - case 'notResponded': - $invite->setStatus(Invite::PENDING); - - break; - - case 'tentativelyAccepted': - $invite->setStatus(Invite::TENTATIVELY_ACCEPTED); - - break; - - case 'accepted': - $invite->setStatus(Invite::ACCEPTED); - - break; - - case 'declined': - $invite->setStatus(Invite::DECLINED); - - break; - - default: - throw new LogicException('should not happens, not implemented: ' . $status); - - break; - } + match ($status) { + 'none', 'notResponded' => $invite->setStatus(Invite::PENDING), + 'tentativelyAccepted' => $invite->setStatus(Invite::TENTATIVELY_ACCEPTED), + 'accepted' => $invite->setStatus(Invite::ACCEPTED), + 'declined' => $invite->setStatus(Invite::DECLINED), + default => throw new \LogicException('should not happens, not implemented: '.$status), + }; } foreach ($calendar->getUsers() as $user) { - if (!in_array(strtolower($user->getEmailCanonical()), $emails, true)) { + if (!\in_array(strtolower($user->getEmailCanonical()), $emails, true)) { $calendar->removeUser($user); } } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php index 0e3beff5f..27297f3ac 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php @@ -28,8 +28,6 @@ use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteEventConverter; use Chill\CalendarBundle\Repository\CalendarRangeRepository; use Chill\CalendarBundle\Repository\CalendarRepository; use Chill\MainBundle\Entity\User; -use DateTimeImmutable; -use Exception; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; @@ -38,62 +36,16 @@ use Symfony\Component\Security\Core\Security; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\Translation\TranslatorInterface; -use function array_key_exists; -use function count; class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface { private array $cacheScheduleTimeForUser = []; - private CalendarRangeRepository $calendarRangeRepository; - - private CalendarRepository $calendarRepository; - - private LoggerInterface $logger; - - private HttpClientInterface $machineHttpClient; - - private MapCalendarToUser $mapCalendarToUser; - - private RemoteEventConverter $remoteEventConverter; - - private OnBehalfOfUserTokenStorage $tokenStorage; - - private TranslatorInterface $translator; - - private UrlGeneratorInterface $urlGenerator; - - private OnBehalfOfUserHttpClient $userHttpClient; - - private Security $security; - - public function __construct( - CalendarRepository $calendarRepository, - CalendarRangeRepository $calendarRangeRepository, - HttpClientInterface $machineHttpClient, - MapCalendarToUser $mapCalendarToUser, - LoggerInterface $logger, - OnBehalfOfUserTokenStorage $tokenStorage, - OnBehalfOfUserHttpClient $userHttpClient, - RemoteEventConverter $remoteEventConverter, - TranslatorInterface $translator, - UrlGeneratorInterface $urlGenerator, - Security $security - ) { - $this->calendarRepository = $calendarRepository; - $this->calendarRangeRepository = $calendarRangeRepository; - $this->machineHttpClient = $machineHttpClient; - $this->mapCalendarToUser = $mapCalendarToUser; - $this->logger = $logger; - $this->remoteEventConverter = $remoteEventConverter; - $this->tokenStorage = $tokenStorage; - $this->translator = $translator; - $this->urlGenerator = $urlGenerator; - $this->userHttpClient = $userHttpClient; - $this->security = $security; + public function __construct(private readonly CalendarRepository $calendarRepository, private readonly CalendarRangeRepository $calendarRangeRepository, private readonly HttpClientInterface $machineHttpClient, private readonly MapCalendarToUser $mapCalendarToUser, private readonly LoggerInterface $logger, private readonly OnBehalfOfUserTokenStorage $tokenStorage, private readonly OnBehalfOfUserHttpClient $userHttpClient, private readonly RemoteEventConverter $remoteEventConverter, private readonly TranslatorInterface $translator, private readonly UrlGeneratorInterface $urlGenerator, private readonly Security $security) + { } - public function countEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): int + public function countEventsForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): int { $userId = $this->mapCalendarToUser->getUserId($user); @@ -104,7 +56,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $data = $this->userHttpClient->request( 'GET', - 'users/' . $userId . '/calendarView', + 'users/'.$userId.'/calendarView', [ 'query' => [ 'startDateTime' => $startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()), @@ -116,7 +68,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface )->toArray(); } catch (ClientExceptionInterface $e) { if (403 === $e->getResponse()->getStatusCode()) { - return count($this->getScheduleTimesForUser($user, $startDate, $endDate)); + return \count($this->getScheduleTimesForUser($user, $startDate, $endDate)); } $this->logger->error('Could not get list of event on MSGraph', [ @@ -153,6 +105,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $this->logger->debug('mark user ready for msgraph calendar as he does not have any mapping', [ 'userId' => $user->getId(), ]); + return true; } @@ -160,14 +113,14 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface } /** + * @return array|\Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent[] + * * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface - * - * @return array|\Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent[] */ - public function listEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate, ?int $offset = 0, ?int $limit = 50): array + public function listEventsForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate, ?int $offset = 0, ?int $limit = 50): array { $userId = $this->mapCalendarToUser->getUserId($user); @@ -178,7 +131,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $bareEvents = $this->userHttpClient->request( 'GET', - 'users/' . $userId . '/calendarView', + 'users/'.$userId.'/calendarView', [ 'query' => [ 'startDateTime' => $startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()), @@ -268,7 +221,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $calendar->getRemoteId(), $this->translator->trans('remote_ms_graph.cancel_event_because_main_user_is_%label%', ['%label%' => $calendar->getMainUser()]), $previousMainUser, - 'calendar_' . $calendar->getRemoteId() + 'calendar_'.$calendar->getRemoteId() ); $this->createCalendarOnRemote($calendar); } else { @@ -346,7 +299,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface break; default: - throw new Exception('not supported'); + throw new \Exception('not supported'); } try { @@ -359,7 +312,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $this->logger->warning('could not update calendar range to remote', [ 'exception' => $e->getTraceAsString(), 'content' => $e->getResponse()->getContent(), - 'calendarRangeId' => 'invite_' . $invite->getId(), + 'calendarRangeId' => 'invite_'.$invite->getId(), ]); throw $e; @@ -401,7 +354,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface 'id' => $id, 'lastModifiedDateTime' => $lastModified, 'changeKey' => $changeKey - ] = $this->createOnRemote($eventData, $calendar->getMainUser(), 'calendar_' . $calendar->getId()); + ] = $this->createOnRemote($eventData, $calendar->getMainUser(), 'calendar_'.$calendar->getId()); if (null === $id) { return; @@ -436,7 +389,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $event = $this->machineHttpClient->request( 'POST', - 'users/' . $userId . '/calendar/events', + 'users/'.$userId.'/calendar/events', [ 'json' => $eventData, ] @@ -480,7 +433,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface ] = $this->createOnRemote( $eventData, $calendarRange->getUser(), - 'calendar_range_' . $calendarRange->getId() + 'calendar_range_'.$calendarRange->getId() ); $calendarRange->setRemoteId($id) @@ -503,7 +456,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $userId = $this->mapCalendarToUser->getUserId($user); if ('' === $iCalUid = ($event['iCalUId'] ?? '')) { - throw new Exception('no iCalUid for this event'); + throw new \Exception('no iCalUid for this event'); } try { @@ -521,8 +474,8 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface throw $clientException; } - if (1 !== count($events['value'])) { - throw new Exception('multiple events found with same iCalUid'); + if (1 !== \count($events['value'])) { + throw new \Exception('multiple events found with same iCalUid'); } return $events['value'][0]['id']; @@ -533,19 +486,13 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $userId = $this->mapCalendarToUser->getUserId($user); if (null === $userId) { - throw new Exception( - sprintf( - 'no remote calendar for this user: %s, remoteid: %s', - $user->getId(), - $remoteId - ) - ); + throw new \Exception(sprintf('no remote calendar for this user: %s, remoteid: %s', $user->getId(), $remoteId)); } try { return $this->machineHttpClient->request( 'GET', - 'users/' . $userId . '/calendar/events/' . $remoteId + 'users/'.$userId.'/calendar/events/'.$remoteId )->toArray(); } catch (ClientExceptionInterface $e) { $this->logger->warning('Could not get event from calendar', [ @@ -556,11 +503,11 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface } } - private function getScheduleTimesForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): array + private function getScheduleTimesForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): array { $userId = $this->mapCalendarToUser->getUserId($user); - if (array_key_exists($userId, $this->cacheScheduleTimeForUser)) { + if (\array_key_exists($userId, $this->cacheScheduleTimeForUser)) { return $this->cacheScheduleTimeForUser[$userId]; } @@ -575,17 +522,17 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $body = [ 'schedules' => [$user->getEmailCanonical()], 'startTime' => [ - 'dateTime' => ($startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat())), + 'dateTime' => $startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()), 'timeZone' => 'UTC', ], 'endTime' => [ - 'dateTime' => ($endDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat())), + 'dateTime' => $endDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()), 'timeZone' => 'UTC', ], ]; try { - $response = $this->userHttpClient->request('POST', 'users/' . $userId . '/calendar/getSchedule', [ + $response = $this->userHttpClient->request('POST', 'users/'.$userId.'/calendar/getSchedule', [ 'json' => $body, ])->toArray(); } catch (ClientExceptionInterface $e) { @@ -610,7 +557,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $eventDatas = []; $eventDatas[] = $this->remoteEventConverter->calendarToEvent($calendar); - if (0 < count($newInvites)) { + if (0 < \count($newInvites)) { // it seems that invitaiton are always send, even if attendee changes are mixed with other datas // $eventDatas[] = $this->remoteEventConverter->calendarToEventAttendeesOnly($calendar); } @@ -624,7 +571,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $calendar->getRemoteId(), $eventData, $calendar->getMainUser(), - 'calendar_' . $calendar->getId() + 'calendar_'.$calendar->getId() ); $calendar->addRemoteAttributes([ @@ -655,7 +602,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $event = $this->machineHttpClient->request( 'PATCH', - 'users/' . $userId . '/calendar/events/' . $remoteId, + 'users/'.$userId.'/calendar/events/'.$remoteId, [ 'json' => $eventData, ] @@ -683,9 +630,9 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $this->machineHttpClient->request( 'DELETE', - 'users/' . $userId . '/calendar/events/' . $remoteId + 'users/'.$userId.'/calendar/events/'.$remoteId ); - } catch (ClientExceptionInterface $e) { + } catch (ClientExceptionInterface) { $this->logger->warning('could not remove event from calendar', [ 'event_remote_id' => $remoteId, 'user_id' => $user->getId(), @@ -710,7 +657,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $event = $this->machineHttpClient->request( 'GET', - 'users/' . $userId . '/calendar/events/' . $calendarRange->getRemoteId() + 'users/'.$userId.'/calendar/events/'.$calendarRange->getRemoteId() )->toArray(); } catch (ClientExceptionInterface $e) { $this->logger->warning('Could not get event from calendar', [ @@ -737,7 +684,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $event = $this->machineHttpClient->request( 'PATCH', - 'users/' . $userId . '/calendar/events/' . $calendarRange->getRemoteId(), + 'users/'.$userId.'/calendar/events/'.$calendarRange->getRemoteId(), [ 'json' => $eventData, ] diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php index 211810abf..7da67df0c 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php @@ -22,20 +22,18 @@ use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\CalendarRange; use Chill\CalendarBundle\Entity\Invite; use Chill\MainBundle\Entity\User; -use DateTimeImmutable; -use LogicException; use Symfony\Component\HttpFoundation\Response; class NullRemoteCalendarConnector implements RemoteCalendarConnectorInterface { - public function countEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): int + public function countEventsForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): int { return 0; } public function getMakeReadyResponse(string $returnPath): Response { - throw new LogicException('As this connector is always ready, this method should not be called'); + throw new \LogicException('As this connector is always ready, this method should not be called'); } public function isReady(): bool @@ -43,7 +41,7 @@ class NullRemoteCalendarConnector implements RemoteCalendarConnectorInterface return true; } - public function listEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate, ?int $offset = 0, ?int $limit = 50): array + public function listEventsForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate, ?int $offset = 0, ?int $limit = 50): array { return []; } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php index e3ef89ca4..1e3c16845 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php @@ -23,12 +23,11 @@ use Chill\CalendarBundle\Entity\CalendarRange; use Chill\CalendarBundle\Entity\Invite; use Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent; use Chill\MainBundle\Entity\User; -use DateTimeImmutable; use Symfony\Component\HttpFoundation\Response; interface RemoteCalendarConnectorInterface { - public function countEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): int; + public function countEventsForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): int; /** * Return a response, more probably a RedirectResponse, where the user @@ -46,7 +45,7 @@ interface RemoteCalendarConnectorInterface /** * @return array|RemoteEvent[] */ - public function listEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate, ?int $offset = 0, ?int $limit = 50): array; + public function listEventsForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate, ?int $offset = 0, ?int $limit = 50): array; public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, ?CalendarRange $associatedCalendarRange = null): void; diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php index 6a986c9db..c52fc2866 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php @@ -28,7 +28,6 @@ use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraphRemoteCalendarConnector; use Chill\CalendarBundle\RemoteCalendar\Connector\NullRemoteCalendarConnector; use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface; -use RuntimeException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -43,7 +42,7 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface if (true === $config['remote_calendars_sync']['microsoft_graph']['enabled']) { $connector = MSGraphRemoteCalendarConnector::class; - $container->setAlias(HttpClientInterface::class . ' $machineHttpClient', MachineHttpClient::class); + $container->setAlias(HttpClientInterface::class.' $machineHttpClient', MachineHttpClient::class); } else { $connector = NullRemoteCalendarConnector::class; // remove services which cannot be loaded diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php index 13c4a1c6a..0c87ae4eb 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php @@ -18,45 +18,32 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Model; -use DateTimeImmutable; use Symfony\Component\Serializer\Annotation as Serializer; class RemoteEvent { - public string $description; - - /** - * @Serializer\Groups({"read"}) - */ - public DateTimeImmutable $endDate; - - /** - * @Serializer\Groups({"read"}) - */ - public string $id; - - /** - * @Serializer\Groups({"read"}) - */ - public bool $isAllDay; - - /** - * @Serializer\Groups({"read"}) - */ - public DateTimeImmutable $startDate; - - /** - * @Serializer\Groups({"read"}) - */ - public string $title; - - public function __construct(string $id, string $title, string $description, DateTimeImmutable $startDate, DateTimeImmutable $endDate, bool $isAllDay = false) - { - $this->id = $id; - $this->title = $title; - $this->description = $description; - $this->startDate = $startDate; - $this->endDate = $endDate; - $this->isAllDay = $isAllDay; + public function __construct( + /** + * @Serializer\Groups({"read"}) + */ + public string $id, + /** + * @Serializer\Groups({"read"}) + */ + public string $title, + public string $description, + /** + * @Serializer\Groups({"read"}) + */ + public \DateTimeImmutable $startDate, + /** + * @Serializer\Groups({"read"}) + */ + public \DateTimeImmutable $endDate, + /** + * @Serializer\Groups({"read"}) + */ + public bool $isAllDay = false + ) { } } diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php index 7c6ef4e13..a3350a7cd 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php @@ -23,25 +23,16 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface { - private AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository; - - private EntityManagerInterface $em; - - public function __construct( - AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository, - EntityManagerInterface $em - ) { - $this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository; - $this->em = $em; + public function __construct(private readonly AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository, private readonly EntityManagerInterface $em) + { } - public function buildQueryByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + public function buildQueryByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): QueryBuilder { $qb = $this->em->createQueryBuilder(); $qb->from(Calendar::class, 'c'); @@ -64,7 +55,7 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface return $qb; } - public function buildQueryByAccompanyingPeriodIgnoredByDates(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + public function buildQueryByAccompanyingPeriodIgnoredByDates(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): QueryBuilder { $qb = $this->em->createQueryBuilder(); $qb->from(Calendar::class, 'c'); @@ -90,7 +81,7 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface /** * Base implementation. The list of allowed accompanying period is retrieved "manually" from @see{AccompanyingPeriodACLAwareRepository}. */ - public function buildQueryByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + public function buildQueryByPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): QueryBuilder { $qb = $this->em->createQueryBuilder() ->from(Calendar::class, 'c'); @@ -114,7 +105,7 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface /** * Base implementation. The list of allowed accompanying period is retrieved "manually" from @see{AccompanyingPeriodACLAwareRepository}. */ - public function buildQueryByPersonIgnoredByDates(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + public function buildQueryByPersonIgnoredByDates(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): QueryBuilder { $qb = $this->em->createQueryBuilder() ->from(Calendar::class, 'c'); @@ -135,14 +126,14 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface return $qb; } - public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int + public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int { $qb = $this->buildQueryByAccompanyingPeriod($period, $startDate, $endDate)->select('count(c)'); return $qb->getQuery()->getSingleScalarResult(); } - public function countByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int + public function countByPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int { return $this->buildQueryByPerson($person, $startDate, $endDate) ->select('COUNT(c)') @@ -150,14 +141,14 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface ->getSingleScalarResult(); } - public function countIgnoredByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int + public function countIgnoredByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int { $qb = $this->buildQueryByAccompanyingPeriodIgnoredByDates($period, $startDate, $endDate)->select('count(c)'); return $qb->getQuery()->getSingleScalarResult(); } - public function countIgnoredByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int + public function countIgnoredByPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int { return $this->buildQueryByPersonIgnoredByDates($person, $startDate, $endDate) ->select('COUNT(c)') @@ -168,12 +159,12 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface /** * @return array|Calendar[] */ - public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array + public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array { $qb = $this->buildQueryByAccompanyingPeriod($period, $startDate, $endDate)->select('c'); foreach ($orderBy as $sort => $order) { - $qb->addOrderBy('c.' . $sort, $order); + $qb->addOrderBy('c.'.$sort, $order); } if (null !== $offset) { @@ -187,13 +178,13 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface return $qb->getQuery()->getResult(); } - public function findByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array + public function findByPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array { $qb = $this->buildQueryByPerson($person, $startDate, $endDate) ->select('c'); foreach ($orderBy as $sort => $order) { - $qb->addOrderBy('c.' . $sort, $order); + $qb->addOrderBy('c.'.$sort, $order); } if (null !== $offset) { diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php index 70fb02590..78f71229d 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php @@ -21,33 +21,32 @@ namespace Chill\CalendarBundle\Repository; use Chill\CalendarBundle\Entity\Calendar; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; -use DateTimeImmutable; interface CalendarACLAwareRepositoryInterface { - public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int; + public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int; /** * Return the number or calendars associated with a person. See condition on @see{self::findByPerson}. */ - public function countByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int; + public function countByPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int; /** * Return the number or calendars associated with an accompanyign period which **does not** match the date conditions. */ - public function countIgnoredByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int; + public function countIgnoredByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int; /** * Return the number or calendars associated with a person which **does not** match the date conditions. * * See condition on @see{self::findByPerson}. */ - public function countIgnoredByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int; + public function countIgnoredByPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int; /** * @return array|Calendar[] */ - public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array; + public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array; /** * Return all the calendars which are associated with a person, either on @see{Calendar::person} or within. @@ -59,5 +58,5 @@ interface CalendarACLAwareRepositoryInterface * * @return array|Calendar[] */ - public function findByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array; + public function findByPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array; } diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php index bd1074b5f..dd593bf3c 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php @@ -18,7 +18,7 @@ use Doctrine\Persistence\ObjectRepository; class CalendarDocRepository implements ObjectRepository, CalendarDocRepositoryInterface { - private EntityRepository $repository; + private readonly EntityRepository $repository; public function __construct(EntityManagerInterface $entityManager) { diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php index dc3405b7e..d6e1ac8a5 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php @@ -13,28 +13,23 @@ namespace Chill\CalendarBundle\Repository; use Chill\CalendarBundle\Entity\CalendarRange; use Chill\MainBundle\Entity\User; -use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; -use function count; class CalendarRangeRepository implements ObjectRepository { - private EntityManagerInterface $em; + private readonly EntityRepository $repository; - private EntityRepository $repository; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(private readonly EntityManagerInterface $em) { - $this->em = $entityManager; - $this->repository = $entityManager->getRepository(CalendarRange::class); + $this->repository = $em->getRepository(CalendarRange::class); } - public function countByAvailableRangesForUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to): int + public function countByAvailableRangesForUser(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): int { return $this->buildQueryAvailableRangesForUser($user, $from, $to) ->select('COUNT(cr)') @@ -67,8 +62,8 @@ class CalendarRangeRepository implements ObjectRepository */ public function findByAvailableRangesForUser( User $user, - DateTimeImmutable $from, - DateTimeImmutable $to, + \DateTimeImmutable $from, + \DateTimeImmutable $to, ?int $limit = null, ?int $offset = null ): array { @@ -101,7 +96,7 @@ class CalendarRangeRepository implements ObjectRepository */ public function findRemoteIdsPresent(array $remoteIds): array { - if (0 === count($remoteIds)) { + if (0 === \count($remoteIds)) { return []; } @@ -116,7 +111,7 @@ class CalendarRangeRepository implements ObjectRepository $remoteIdsStr = implode( ', ', - array_fill(0, count($remoteIds), '((?))') + array_fill(0, \count($remoteIds), '((?))') ); $rsm = new ResultSetMapping(); @@ -146,7 +141,7 @@ class CalendarRangeRepository implements ObjectRepository return CalendarRange::class; } - private function buildQueryAvailableRangesForUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to): QueryBuilder + private function buildQueryAvailableRangesForUser(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): QueryBuilder { $qb = $this->repository->createQueryBuilder('cr'); diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php index 4fd9b8a29..3121854e5 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php @@ -14,20 +14,18 @@ namespace Chill\CalendarBundle\Repository; use Chill\CalendarBundle\Entity\Calendar; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; -use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; -use function count; class CalendarRepository implements ObjectRepository { - private EntityManagerInterface $em; + private readonly EntityManagerInterface $em; - private EntityRepository $repository; + private readonly EntityRepository $repository; public function __construct(EntityManagerInterface $entityManager) { @@ -40,7 +38,7 @@ class CalendarRepository implements ObjectRepository return $this->repository->count(['accompanyingPeriod' => $period]); } - public function countByUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to): int + public function countByUser(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): int { return $this->buildQueryByUser($user, $from, $to) ->select('COUNT(c)') @@ -89,7 +87,7 @@ class CalendarRepository implements ObjectRepository ); } - public function findByNotificationAvailable(DateTimeImmutable $startDate, DateTimeImmutable $endDate, ?int $limit = null, ?int $offset = null): array + public function findByNotificationAvailable(\DateTimeImmutable $startDate, \DateTimeImmutable $endDate, ?int $limit = null, ?int $offset = null): array { $qb = $this->queryByNotificationAvailable($startDate, $endDate)->select('c'); @@ -107,7 +105,7 @@ class CalendarRepository implements ObjectRepository /** * @return array|Calendar[] */ - public function findByUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to, ?int $limit = null, ?int $offset = null): array + public function findByUser(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to, ?int $limit = null, ?int $offset = null): array { $qb = $this->buildQueryByUser($user, $from, $to)->select('c'); @@ -138,13 +136,13 @@ class CalendarRepository implements ObjectRepository */ public function findRemoteIdsPresent(array $remoteIds): array { - if (0 === count($remoteIds)) { + if (0 === \count($remoteIds)) { return []; } $remoteIdsStr = implode( ', ', - array_fill(0, count($remoteIds), '((?))') + array_fill(0, \count($remoteIds), '((?))') ); $sql = "SELECT @@ -183,7 +181,7 @@ class CalendarRepository implements ObjectRepository return Calendar::class; } - private function buildQueryByUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to): QueryBuilder + private function buildQueryByUser(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): QueryBuilder { $qb = $this->repository->createQueryBuilder('c'); @@ -202,7 +200,7 @@ class CalendarRepository implements ObjectRepository ]); } - private function queryByNotificationAvailable(DateTimeImmutable $startDate, DateTimeImmutable $endDate): QueryBuilder + private function queryByNotificationAvailable(\DateTimeImmutable $startDate, \DateTimeImmutable $endDate): QueryBuilder { $qb = $this->repository->createQueryBuilder('c'); diff --git a/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php b/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php index f0bd8fe88..8778330f8 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php @@ -18,7 +18,7 @@ use Doctrine\Persistence\ObjectRepository; class InviteRepository implements ObjectRepository { - private EntityRepository $entityRepository; + private readonly EntityRepository $entityRepository; public function __construct(EntityManagerInterface $em) { diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services/fixtures.yml b/src/Bundle/ChillCalendarBundle/Resources/config/services/fixtures.yml index 2bef0bdc4..c57011511 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/config/services/fixtures.yml +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services/fixtures.yml @@ -1,13 +1,10 @@ --- services: - Chill\CalendarBundle\DataFixtures\ORM\LoadCancelReason: - tags: - - { 'name': doctrine.fixture.orm } - Chill\CalendarBundle\DataFixtures\ORM\LoadInvite: - tags: - - { 'name': doctrine.fixture.orm } - Chill\CalendarBundle\DataFixtures\ORM\LoadCalendarRange: - autowire: true - autoconfigure: true - tags: - - { 'name': doctrine.fixture.orm } \ No newline at end of file + _defaults: + autowire: true + autoconfigure: true + + Chill\CalendarBundle\DataFixtures\ORM\: + resource: './../../../DataFixtures/ORM' + tags: + - { 'name': doctrine.fixture.orm } diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig index 2c3cde01a..9e87af8ef 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig @@ -69,7 +69,7 @@ or calendar.users|length > 0 %}
      - {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { + {% include '@ChillActivity/Activity/concernedGroups.html.twig' with { 'context': calendar.context == 'person' ? 'calendar_person' : 'calendar_accompanyingCourse', 'render': 'wrap-list', 'entity': calendar diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByAccompanyingCourse.html.twig index a167d5c18..728509909 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByAccompanyingCourse.html.twig @@ -7,7 +7,7 @@ {% block content %}
      - {% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'accompanyingCourse'} %} + {% include '@ChillCalendar/Calendar/edit.html.twig' with {'context': 'accompanyingCourse'} %}
      {% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByPerson.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByPerson.html.twig index fc5319849..47d29d9bd 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByPerson.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByPerson.html.twig @@ -7,7 +7,7 @@ {% block content %}
      - {% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'person'} %} + {% include '@ChillCalendar/Calendar/edit.html.twig' with {'context': 'person'} %}
      {% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByUser.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByUser.html.twig index 0e2170c20..3b3a92847 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByUser.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByUser.html.twig @@ -6,7 +6,7 @@
      {# <=== vue component #} - {% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'user'} %} + {% include '@ChillCalendar/Calendar/edit.html.twig' with {'context': 'user'} %}
      {% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByAccompanyingCourse.html.twig index 43fe6cb04..4c206c706 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByAccompanyingCourse.html.twig @@ -8,7 +8,7 @@
      {# <=== vue component #} - {% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'accompanyingCourse'} %} + {% include '@ChillCalendar/Calendar/new.html.twig' with {'context': 'accompanyingCourse'} %}
      {% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByPerson.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByPerson.html.twig index b561e6aa7..a8d851656 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByPerson.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByPerson.html.twig @@ -8,7 +8,7 @@
      {# <=== vue component #} - {% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'person'} %} + {% include '@ChillCalendar/Calendar/new.html.twig' with {'context': 'person'} %}
      {% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig index d48844f67..df1cbb2df 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig @@ -15,7 +15,7 @@

      {{ 'Concerned groups calendar'|trans }}

      -{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': 'calendar_' ~ context, 'render': 'bloc' } %} +{% include '@ChillActivity/Activity/concernedGroups.html.twig' with {'context': 'calendar_' ~ context, 'render': 'bloc' } %}

      {{ 'Calendar data'|trans }}

      diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/showByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/showByAccompanyingCourse.html.twig index fa0dd01af..b056ba998 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/showByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/showByAccompanyingCourse.html.twig @@ -7,7 +7,7 @@ {% block content -%}
      - {% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'accompanyingCourse'} %} + {% include '@ChillCalendar/Calendar/show.html.twig' with {'context': 'accompanyingCourse'} %}
      {% endblock content %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/showByUser.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/showByUser.html.twig index b8e172030..536ccc03a 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/showByUser.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/showByUser.html.twig @@ -5,7 +5,7 @@ {% block content -%}
      - {% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'user'} %} + {% include '@ChillCalendar/Calendar/show.html.twig' with {'context': 'user'} %}
      {% endblock content %} diff --git a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php index 452286e85..5eaa85871 100644 --- a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php @@ -15,47 +15,36 @@ use Chill\CalendarBundle\Entity\CalendarDoc; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Security; -use UnexpectedValueException; -use function in_array; class CalendarDocVoter extends Voter { - public const EDIT = 'CHILL_CALENDAR_DOC_EDIT'; + final public const EDIT = 'CHILL_CALENDAR_DOC_EDIT'; - public const SEE = 'CHILL_CALENDAR_DOC_SEE'; + final public const SEE = 'CHILL_CALENDAR_DOC_SEE'; private const ALL = [ 'CHILL_CALENDAR_DOC_EDIT', 'CHILL_CALENDAR_DOC_SEE', ]; - private Security $security; - - public function __construct(Security $security) + public function __construct(private readonly Security $security) { - $this->security = $security; } protected function supports($attribute, $subject): bool { - return in_array($attribute, self::ALL, true) && $subject instanceof CalendarDoc; + return \in_array($attribute, self::ALL, true) && $subject instanceof CalendarDoc; } /** * @param CalendarDoc $subject - * @param mixed $attribute */ protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool { - switch ($attribute) { - case self::EDIT: - return $this->security->isGranted(CalendarVoter::EDIT, $subject->getCalendar()); - - case self::SEE: - return $this->security->isGranted(CalendarVoter::SEE, $subject->getCalendar()); - - default: - throw new UnexpectedValueException('Attribute not supported: ' . $attribute); - } + return match ($attribute) { + self::EDIT => $this->security->isGranted(CalendarVoter::EDIT, $subject->getCalendar()), + self::SEE => $this->security->isGranted(CalendarVoter::SEE, $subject->getCalendar()), + default => throw new \UnexpectedValueException('Attribute not supported: '.$attribute), + }; } } diff --git a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php index 0e2be1d0f..8f4c3a065 100644 --- a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php @@ -20,41 +20,31 @@ namespace Chill\CalendarBundle\Security\Voter; use Chill\CalendarBundle\Entity\Calendar; use Chill\MainBundle\Security\Authorization\AbstractChillVoter; -use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface; use Chill\MainBundle\Security\Authorization\VoterHelperInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; -use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; -use LogicException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Security; class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface { - public const CREATE = 'CHILL_CALENDAR_CALENDAR_CREATE'; + final public const CREATE = 'CHILL_CALENDAR_CALENDAR_CREATE'; - public const DELETE = 'CHILL_CALENDAR_CALENDAR_DELETE'; + final public const DELETE = 'CHILL_CALENDAR_CALENDAR_DELETE'; - public const EDIT = 'CHILL_CALENDAR_CALENDAR_EDIT'; + final public const EDIT = 'CHILL_CALENDAR_CALENDAR_EDIT'; - public const SEE = 'CHILL_CALENDAR_CALENDAR_SEE'; + final public const SEE = 'CHILL_CALENDAR_CALENDAR_SEE'; - private AuthorizationHelperInterface $authorizationHelper; - - private CenterResolverManagerInterface $centerResolverManager; - - private Security $security; - - private VoterHelperInterface $voterHelper; + private readonly VoterHelperInterface $voterHelper; public function __construct( - Security $security, - VoterHelperFactoryInterface $voterHelperFactory + private readonly Security $security, + VoterHelperFactoryInterface $voterHelperFactory, ) { - $this->security = $security; $this->voterHelper = $voterHelperFactory ->generate(self::class) ->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE]) @@ -99,7 +89,7 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn switch ($attribute) { case self::SEE: case self::CREATE: - if ($subject->getStep() === AccompanyingPeriod::STEP_DRAFT) { + if (AccompanyingPeriod::STEP_DRAFT === $subject->getStep()) { return false; } @@ -129,6 +119,6 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn } } - throw new LogicException('attribute or not implemented'); + throw new \LogicException('attribute or not implemented'); } } diff --git a/src/Bundle/ChillCalendarBundle/Security/Voter/InviteVoter.php b/src/Bundle/ChillCalendarBundle/Security/Voter/InviteVoter.php index 36ca66d05..72aa806e6 100644 --- a/src/Bundle/ChillCalendarBundle/Security/Voter/InviteVoter.php +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/InviteVoter.php @@ -24,7 +24,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; class InviteVoter extends Voter { - public const ANSWER = 'CHILL_CALENDAR_INVITE_ANSWER'; + final public const ANSWER = 'CHILL_CALENDAR_INVITE_ANSWER'; protected function supports($attribute, $subject): bool { diff --git a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php index cabb7dcce..27cfb05e6 100644 --- a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php +++ b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php @@ -29,44 +29,19 @@ use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use function count; -final class CalendarContext implements CalendarContextInterface +final readonly class CalendarContext implements CalendarContextInterface { - private BaseContextData $baseContextData; - - private EntityManagerInterface $entityManager; - - private NormalizerInterface $normalizer; - - private PersonRender $personRender; - - private PersonRepository $personRepository; - - private ThirdPartyRepository $thirdPartyRepository; - - private ThirdPartyRender $thirdPartyRender; - - private TranslatableStringHelperInterface $translatableStringHelper; - public function __construct( - BaseContextData $baseContextData, - EntityManagerInterface $entityManager, - NormalizerInterface $normalizer, - PersonRender $personRender, - PersonRepository $personRepository, - ThirdPartyRender $thirdPartyRender, - ThirdPartyRepository $thirdPartyRepository, - TranslatableStringHelperInterface $translatableStringHelper + private BaseContextData $baseContextData, + private EntityManagerInterface $entityManager, + private NormalizerInterface $normalizer, + private PersonRender $personRender, + private PersonRepository $personRepository, + private ThirdPartyRender $thirdPartyRender, + private ThirdPartyRepository $thirdPartyRepository, + private TranslatableStringHelperInterface $translatableStringHelper ) { - $this->baseContextData = $baseContextData; - $this->entityManager = $entityManager; - $this->normalizer = $normalizer; - $this->personRender = $personRender; - $this->personRepository = $personRepository; - $this->thirdPartyRender = $thirdPartyRender; - $this->thirdPartyRepository = $thirdPartyRepository; - $this->translatableStringHelper = $translatableStringHelper; } public function adminFormReverseTransform(array $data): array @@ -147,8 +122,6 @@ final class CalendarContext implements CalendarContextInterface } } - /** - */ public function getData(DocGeneratorTemplate $template, mixed $entity, array $contextGenerationData = []): array { $options = $this->getOptions($template); @@ -198,7 +171,7 @@ final class CalendarContext implements CalendarContextInterface if ($options['askMainPerson']) { $data['mainPerson'] = null; - if (1 === count($entity->getPersons())) { + if (1 === \count($entity->getPersons())) { $data['mainPerson'] = $entity->getPersons()->first(); } } @@ -206,7 +179,7 @@ final class CalendarContext implements CalendarContextInterface if ($options['askThirdParty']) { $data['thirdParty'] = null; - if (1 === count($entity->getProfessionals())) { + if (1 === \count($entity->getProfessionals())) { $data['thirdParty'] = $entity->getProfessionals()->first(); } } diff --git a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php index 527203003..917a0b5fa 100644 --- a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php +++ b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php @@ -12,12 +12,8 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Service\DocGenerator; use Chill\CalendarBundle\Entity\Calendar; -use Chill\DocGeneratorBundle\Context\DocGeneratorContextInterface; use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface; use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface; -use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; -use Chill\DocStoreBundle\Entity\StoredObject; -use Symfony\Component\Form\FormBuilderInterface; /** * @extends DocGeneratorContextWithPublicFormInterface diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php index c33ccd853..faca60a67 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -22,7 +22,6 @@ use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; -use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\MappingException; @@ -92,7 +91,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod); } - public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + public function buildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); @@ -137,8 +136,8 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen $storedObjectMetadata->getColumnName('createdAt') ); $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), - DateTimeImmutable::createFromInterface($participation->getStartDate()), - null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; + \DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : \DateTimeImmutable::createFromInterface($participation->getEndDate())]; $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; } @@ -147,6 +146,9 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen return $query; } + + $query->addWhereClause(implode(' OR ', $or), $orParams, $orTypes); + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); } @@ -157,7 +159,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); } - private function addWhereClausesToQuery(FetchQuery $query, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?string $content): FetchQuery + private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); @@ -180,13 +182,11 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen if (null !== $content) { $query->addWhereClause( sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), - ['%' . $content . '%'], + ['%'.$content.'%'], [Types::STRING] ); } return $query; } - - } diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php index f5d4b3cbb..f088e27ba 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php @@ -19,8 +19,6 @@ use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; -use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\MappingException; @@ -28,7 +26,7 @@ use Service\GenericDoc\Providers\PersonCalendarGenericDocProviderTest; use Symfony\Component\Security\Core\Security; /** - * Provide calendar documents for calendar associated to persons + * Provide calendar documents for calendar associated to persons. * * @see PersonCalendarGenericDocProviderTest */ @@ -65,7 +63,7 @@ final readonly class PersonCalendarGenericDocProvider implements GenericDocForPe if (null !== $content) { $query->addWhereClause( sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), - ['%' . $content . '%'], + ['%'.$content.'%'], [Types::STRING] ); } @@ -76,7 +74,7 @@ final readonly class PersonCalendarGenericDocProvider implements GenericDocForPe /** * @throws MappingException */ - public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + public function buildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); @@ -115,10 +113,6 @@ final readonly class PersonCalendarGenericDocProvider implements GenericDocForPe return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); } - /** - * @param Person $person - * @return bool - */ public function isAllowedForPerson(Person $person): bool { return $this->security->isGranted(CalendarVoter::SEE, $person); diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php index b15c091c6..1f4c3a45f 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php @@ -17,18 +17,15 @@ use Chill\CalendarBundle\Service\GenericDoc\Providers\PersonCalendarGenericDocPr use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; -final class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRendererInterface +final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRendererInterface { - private CalendarDocRepository $repository; - - public function __construct(CalendarDocRepository $calendarDocRepository) + public function __construct(private CalendarDocRepository $repository) { - $this->repository = $calendarDocRepository; } public function supports(GenericDocDTO $genericDocDTO, $options = []): bool { - return $genericDocDTO->key === AccompanyingPeriodCalendarGenericDocProvider::KEY || $genericDocDTO->key === PersonCalendarGenericDocProvider::KEY; + return AccompanyingPeriodCalendarGenericDocProvider::KEY === $genericDocDTO->key || PersonCalendarGenericDocProvider::KEY === $genericDocDTO->key; } public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php index 83826bbcf..b03a023d8 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php @@ -19,30 +19,14 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Service\ShortMessageNotification; use Chill\CalendarBundle\Entity\Calendar; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Messenger\MessageBusInterface; class BulkCalendarShortMessageSender { - private EntityManagerInterface $em; - - private LoggerInterface $logger; - - private MessageBusInterface $messageBus; - - private ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder; - - private CalendarForShortMessageProvider $provider; - - public function __construct(CalendarForShortMessageProvider $provider, EntityManagerInterface $em, LoggerInterface $logger, MessageBusInterface $messageBus, ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder) + public function __construct(private readonly CalendarForShortMessageProvider $provider, private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly MessageBusInterface $messageBus, private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder) { - $this->provider = $provider; - $this->em = $em; - $this->logger = $logger; - $this->messageBus = $messageBus; - $this->messageForCalendarBuilder = $messageForCalendarBuilder; } public function sendBulkMessageToEligibleCalendars() @@ -50,7 +34,7 @@ class BulkCalendarShortMessageSender $countCalendars = 0; $countSms = 0; - foreach ($this->provider->getCalendars(new DateTimeImmutable('now')) as $calendar) { + foreach ($this->provider->getCalendars(new \DateTimeImmutable('now')) as $calendar) { $smses = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar); foreach ($smses as $sms) { @@ -59,13 +43,13 @@ class BulkCalendarShortMessageSender } $this->em - ->createQuery('UPDATE ' . Calendar::class . ' c SET c.smsStatus = :smsStatus WHERE c.id = :id') + ->createQuery('UPDATE '.Calendar::class.' c SET c.smsStatus = :smsStatus WHERE c.id = :id') ->setParameters(['smsStatus' => Calendar::SMS_SENT, 'id' => $calendar->getId()]) ->execute(); ++$countCalendars; $this->em->refresh($calendar); } - $this->logger->info(self::class . 'a bulk of messages was sent', ['count_calendars' => $countCalendars, 'count_sms' => $countSms]); + $this->logger->info(self::class.'a bulk of messages was sent', ['count_calendars' => $countCalendars, 'count_sms' => $countSms]); } } diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php index 2316a5408..85bc74efc 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php @@ -20,26 +20,12 @@ namespace Chill\CalendarBundle\Service\ShortMessageNotification; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Repository\CalendarRepository; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; -use function count; class CalendarForShortMessageProvider { - private CalendarRepository $calendarRepository; - - private EntityManagerInterface $em; - - private RangeGeneratorInterface $rangeGenerator; - - public function __construct( - CalendarRepository $calendarRepository, - EntityManagerInterface $em, - RangeGeneratorInterface $rangeGenerator - ) { - $this->calendarRepository = $calendarRepository; - $this->em = $em; - $this->rangeGenerator = $rangeGenerator; + public function __construct(private readonly CalendarRepository $calendarRepository, private readonly EntityManagerInterface $em, private readonly RangeGeneratorInterface $rangeGenerator) + { } /** @@ -49,7 +35,7 @@ class CalendarForShortMessageProvider * * @return iterable|Calendar[] */ - public function getCalendars(DateTimeImmutable $at): iterable + public function getCalendars(\DateTimeImmutable $at): iterable { $range = $this->rangeGenerator->generateRange($at); @@ -76,6 +62,6 @@ class CalendarForShortMessageProvider $calendars = $this->calendarRepository ->findByNotificationAvailable($startDate, $endDate, $batchSize, $offset); - } while (count($calendars) === $batchSize); + } while (\count($calendars) === $batchSize); } } diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php index 7d6da3ef0..b79118cf0 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php @@ -18,9 +18,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Service\ShortMessageNotification; -use DateInterval; use Monolog\DateTimeImmutable; -use UnexpectedValueException; /** * * Lundi => Envoi des rdv du mardi et mercredi. @@ -33,7 +31,7 @@ class DefaultRangeGenerator implements RangeGeneratorInterface { public function generateRange(\DateTimeImmutable $date): ?array { - $onMidnight = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d') . ' 00:00:00'); + $onMidnight = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d').' 00:00:00'); switch ($dow = (int) $onMidnight->format('w')) { case 6: // Saturday @@ -42,32 +40,32 @@ class DefaultRangeGenerator implements RangeGeneratorInterface case 1: // Monday // send for Tuesday and Wednesday - $startDate = $onMidnight->add(new DateInterval('P1D')); - $endDate = $startDate->add(new DateInterval('P2D')); + $startDate = $onMidnight->add(new \DateInterval('P1D')); + $endDate = $startDate->add(new \DateInterval('P2D')); break; case 2: // tuesday case 3: // wednesday - $startDate = $onMidnight->add(new DateInterval('P2D')); - $endDate = $startDate->add(new DateInterval('P1D')); + $startDate = $onMidnight->add(new \DateInterval('P2D')); + $endDate = $startDate->add(new \DateInterval('P1D')); break; case 4: // thursday - $startDate = $onMidnight->add(new DateInterval('P2D')); - $endDate = $startDate->add(new DateInterval('P2D')); + $startDate = $onMidnight->add(new \DateInterval('P2D')); + $endDate = $startDate->add(new \DateInterval('P2D')); break; case 5: // friday - $startDate = $onMidnight->add(new DateInterval('P3D')); - $endDate = $startDate->add(new DateInterval('P1D')); + $startDate = $onMidnight->add(new \DateInterval('P3D')); + $endDate = $startDate->add(new \DateInterval('P1D')); break; default: - throw new UnexpectedValueException('a day of a week should not have the value: ' . $dow); + throw new \UnexpectedValueException('a day of a week should not have the value: '.$dow); } return ['startDate' => $startDate, 'endDate' => $endDate]; diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php index cd61a8624..6c402ffe3 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php @@ -20,16 +20,11 @@ namespace Chill\CalendarBundle\Service\ShortMessageNotification; use Chill\CalendarBundle\Entity\Calendar; use Chill\MainBundle\Service\ShortMessage\ShortMessage; -use Symfony\Component\Templating\EngineInterface; class DefaultShortMessageForCalendarBuilder implements ShortMessageForCalendarBuilderInterface { - private EngineInterface $engine; - - public function __construct( - EngineInterface $engine - ) { - $this->engine = $engine; + public function __construct(private readonly \Twig\Environment $engine) + { } public function buildMessageForCalendar(Calendar $calendar): array diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/RangeGeneratorInterface.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/RangeGeneratorInterface.php index 357e264d9..a96664907 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/RangeGeneratorInterface.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/RangeGeneratorInterface.php @@ -18,12 +18,10 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Service\ShortMessageNotification; -use DateTimeImmutable; - interface RangeGeneratorInterface { /** - * @return ?array{startDate: DateTimeImmutable, endDate: DateTimeImmutable} when return is null, then no ShortMessage must be send + * @return ?array{startDate: \DateTimeImmutable, endDate: \DateTimeImmutable} when return is null, then no ShortMessage must be send */ - public function generateRange(DateTimeImmutable $date): ?array; + public function generateRange(\DateTimeImmutable $date): ?array; } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Controller/CalendarControllerTest.php b/src/Bundle/ChillCalendarBundle/Tests/Controller/CalendarControllerTest.php index f53fe1dea..80d01ada5 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Controller/CalendarControllerTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Controller/CalendarControllerTest.php @@ -11,28 +11,35 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Tests\Controller; +use Chill\MainBundle\Test\PrepareClientTrait; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; -use function random_int; /** * @internal + * * @coversNothing */ final class CalendarControllerTest extends WebTestCase { + use PrepareClientTrait; + + private KernelBrowser $client; + /** * Setup before each test method (see phpunit doc). */ protected function setUp(): void { - self::bootKernel(); - $this->client = self::createClient([], [ - 'PHP_AUTH_USER' => 'center a_social', - 'PHP_AUTH_PW' => 'password', - ]); + $this->client = $this->getClientAuthenticated(); + } + + protected function tearDown(): void + { + self::ensureKernelShutdown(); } public function provideAccompanyingPeriod(): iterable @@ -42,18 +49,38 @@ final class CalendarControllerTest extends WebTestCase $nb = $em->createQueryBuilder() ->from(AccompanyingPeriod::class, 'ac') - ->select('COUNT(ac) AS nb') + ->join('ac.participations', 'acp') + ->join('acp.person', 'person') + ->join('person.centerCurrent', 'pc') + ->join('pc.center', 'center') + ->where('center.name LIKE :n') + ->setParameter('n', 'Center A') + ->join('ac.scopes', 's') + ->andWhere('JSON_EXTRACT(s.name,\'fr\') LIKE :s') + ->setParameter('s', 'social') + ->select('COUNT(DISTINCT ac) AS nb') ->getQuery() ->getSingleScalarResult(); yield [$em->createQueryBuilder() ->from(AccompanyingPeriod::class, 'ac') ->select('ac.id') - ->setFirstResult(random_int(0, $nb)) + ->join('ac.participations', 'acp') + ->join('acp.person', 'person') + ->join('person.centerCurrent', 'pc') + ->join('pc.center', 'center') + ->where('center.name LIKE :n') + ->setParameter('n', 'Center A') + ->join('ac.scopes', 's') + ->andWhere('JSON_EXTRACT(s.name,\'fr\') LIKE :s') + ->setParameter('s', 'social') + ->setFirstResult(\random_int(0, $nb - 1)) ->setMaxResults(1) ->getQuery() ->getSingleScalarResult(), ]; + + self::ensureKernelShutdown(); } /** @@ -63,7 +90,7 @@ final class CalendarControllerTest extends WebTestCase { $this->client->request( Request::METHOD_GET, - sprintf('/fr/calendar/calendar/?accompanying_period_id=%d', $accompanyingPeriodId) + sprintf('/fr/calendar/calendar/by-period/%d', $accompanyingPeriodId) ); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); diff --git a/src/Bundle/ChillCalendarBundle/Tests/Controller/RemoteCalendarMSGraphSyncControllerTest.php b/src/Bundle/ChillCalendarBundle/Tests/Controller/RemoteCalendarMSGraphSyncControllerTest.php index e805f71aa..0c53f2b8d 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Controller/RemoteCalendarMSGraphSyncControllerTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Controller/RemoteCalendarMSGraphSyncControllerTest.php @@ -23,6 +23,7 @@ use Symfony\Component\Messenger\Transport\InMemoryTransport; /** * @internal + * * @coversNothing */ final class RemoteCalendarMSGraphSyncControllerTest extends WebTestCase @@ -48,9 +49,9 @@ final class RemoteCalendarMSGraphSyncControllerTest extends WebTestCase } JSON; - protected function setUp(): void + protected function tearDown(): void { - self::bootKernel(); + self::ensureKernelShutdown(); } public function testSendNotification(): void diff --git a/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php index fa6132b03..98306b10c 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php @@ -18,6 +18,7 @@ use PHPUnit\Framework\TestCase; /** * @internal + * * @coversNothing */ final class CalendarDocTest extends TestCase diff --git a/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarTest.php b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarTest.php index e37436ad3..1cedea31c 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarTest.php @@ -24,6 +24,7 @@ use PHPUnit\Framework\TestCase; /** * @internal + * * @coversNothing */ final class CalendarTest extends TestCase diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/AgentAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/AgentAggregatorTest.php index 6746282a4..ae1aa6663 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/AgentAggregatorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/AgentAggregatorTest.php @@ -25,6 +25,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class AgentAggregatorTest extends AbstractAggregatorTest @@ -33,7 +34,7 @@ final class AgentAggregatorTest extends AbstractAggregatorTest protected function setUp(): void { - parent::setUp(); + self::bootKernel(); $this->aggregator = self::$container->get('chill.calendar.export.agent_aggregator'); } @@ -52,17 +53,14 @@ final class AgentAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(cal.id)') - ->from(Calendar::class, 'cal') - ->join('cal.mainUser', 'caluser'), + ->from(Calendar::class, 'cal'), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/CancelReasonAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/CancelReasonAggregatorTest.php index 956a81174..0cc1ba58a 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/CancelReasonAggregatorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/CancelReasonAggregatorTest.php @@ -25,6 +25,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class CancelReasonAggregatorTest extends AbstractAggregatorTest @@ -33,7 +34,7 @@ final class CancelReasonAggregatorTest extends AbstractAggregatorTest protected function setUp(): void { - parent::setUp(); + self::bootKernel(); $this->aggregator = self::$container->get('chill.calendar.export.cancel_reason_aggregator'); } @@ -52,17 +53,14 @@ final class CancelReasonAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(cal.id)') - ->from(Calendar::class, 'cal') - ->join('cal.cancelReason', 'calcancel'), + ->from(Calendar::class, 'cal'), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/JobAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/JobAggregatorTest.php index b389b1536..50949c3e3 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/JobAggregatorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/JobAggregatorTest.php @@ -25,6 +25,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class JobAggregatorTest extends AbstractAggregatorTest @@ -33,7 +34,7 @@ final class JobAggregatorTest extends AbstractAggregatorTest protected function setUp(): void { - parent::setUp(); + self::bootKernel(); $this->aggregator = self::$container->get('chill.calendar.export.job_aggregator'); } @@ -52,17 +53,14 @@ final class JobAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(cal.id)') - ->from(Calendar::class, 'cal') - ->join('cal.user', 'caluser'), + ->from(Calendar::class, 'cal'), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationAggregatorTest.php index 50ee456e5..c46f9d6da 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationAggregatorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationAggregatorTest.php @@ -25,6 +25,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class LocationAggregatorTest extends AbstractAggregatorTest @@ -33,7 +34,7 @@ final class LocationAggregatorTest extends AbstractAggregatorTest protected function setUp(): void { - parent::setUp(); + self::bootKernel(); $this->aggregator = self::$container->get('chill.calendar.export.location_aggregator'); } @@ -52,17 +53,14 @@ final class LocationAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(cal.id)') - ->from(Calendar::class, 'cal') - ->join('cal.location', 'calloc'), + ->from(Calendar::class, 'cal'), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php index 55dcf317f..fcf4e2709 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php @@ -25,6 +25,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class LocationTypeAggregatorTest extends AbstractAggregatorTest @@ -33,7 +34,7 @@ final class LocationTypeAggregatorTest extends AbstractAggregatorTest protected function setUp(): void { - parent::setUp(); + self::bootKernel(); $this->aggregator = self::$container->get('chill.calendar.export.location_type_aggregator'); } @@ -52,17 +53,14 @@ final class LocationTypeAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(cal.id)') - ->from(Calendar::class, 'cal') - ->join('cal.location', 'calloc'), + ->from(Calendar::class, 'cal'), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/MonthYearAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/MonthYearAggregatorTest.php index 8e016b54c..7c50a0b52 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/MonthYearAggregatorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/MonthYearAggregatorTest.php @@ -25,6 +25,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class MonthYearAggregatorTest extends AbstractAggregatorTest @@ -33,7 +34,7 @@ final class MonthYearAggregatorTest extends AbstractAggregatorTest protected function setUp(): void { - parent::setUp(); + self::bootKernel(); $this->aggregator = self::$container->get('chill.calendar.export.month_aggregator'); } @@ -52,9 +53,7 @@ final class MonthYearAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/ScopeAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/ScopeAggregatorTest.php index fa816f4a5..77ffc0bf1 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/ScopeAggregatorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/ScopeAggregatorTest.php @@ -25,6 +25,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class ScopeAggregatorTest extends AbstractAggregatorTest @@ -33,7 +34,7 @@ final class ScopeAggregatorTest extends AbstractAggregatorTest protected function setUp(): void { - parent::setUp(); + self::bootKernel(); $this->aggregator = self::$container->get('chill.calendar.export.scope_aggregator'); } @@ -52,17 +53,14 @@ final class ScopeAggregatorTest extends AbstractAggregatorTest public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('count(cal.id)') - ->from(Calendar::class, 'cal') - ->join('cal.user', 'caluser'), + ->from(Calendar::class, 'cal'), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/AgentFilterTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/AgentFilterTest.php index d5ec9b22d..3d4efdd83 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/AgentFilterTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/AgentFilterTest.php @@ -26,6 +26,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class AgentFilterTest extends AbstractFilterTest @@ -50,8 +51,9 @@ final class AgentFilterTest extends AbstractFilterTest return $this->filter; } - public function getFormData(): array + public function getFormData(): iterable { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() @@ -63,26 +65,25 @@ final class AgentFilterTest extends AbstractFilterTest $data = []; foreach ($array as $a) { - $data[] = [ + yield [ 'accepted_agents' => $a, ]; } - return $data; + self::ensureKernelShutdown(); } - public function getQueryBuilders(): array + public function getQueryBuilders(): iterable { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); - return [ - $em->createQueryBuilder() - ->select('cal.id') - ->from(Calendar::class, 'cal'), - ]; + yield $em->createQueryBuilder() + ->select('cal.id') + ->from(Calendar::class, 'cal') + ; + + self::ensureKernelShutdown(); } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/BetweenDatesFilterTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/BetweenDatesFilterTest.php index 85db16c72..4a7751433 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/BetweenDatesFilterTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/BetweenDatesFilterTest.php @@ -20,12 +20,13 @@ namespace Chill\CalendarBundle\Tests\Export\Filter; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Export\Filter\BetweenDatesFilter; +use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Test\Export\AbstractFilterTest; -use DateTime; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class BetweenDatesFilterTest extends AbstractFilterTest @@ -54,17 +55,15 @@ final class BetweenDatesFilterTest extends AbstractFilterTest { return [ [ - 'date_from' => DateTime::createFromFormat('Y-m-d', '2022-05-01'), - 'date_to' => DateTime::createFromFormat('Y-m-d', '2022-06-01'), + 'date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'date_to' => new RollingDate(RollingDate::T_TODAY), ], ]; } public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/JobFilterTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/JobFilterTest.php index 61cb47120..e4bf1ac3b 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/JobFilterTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/JobFilterTest.php @@ -22,10 +22,12 @@ use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Export\Filter\JobFilter; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Test\Export\AbstractFilterTest; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class JobFilterTest extends AbstractFilterTest @@ -50,40 +52,35 @@ final class JobFilterTest extends AbstractFilterTest return $this->filter; } - public function getFormData(): array + public function getFormData(): iterable { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(UserJob::class, 'uj') ->select('uj') ->getQuery() + ->setMaxResults(1) ->getResult(); - $data = []; - - foreach ($array as $a) { - $data[] = [ - 'job' => $a, - ]; - } - - return $data; + return [ + [ + 'job' => new ArrayCollection($array), + ], + ]; } public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('cal.id') - ->from(Calendar::class, 'cal') - ->join('cal.user', 'caluser'), + ->from(Calendar::class, 'cal'), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/ScopeFilterTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/ScopeFilterTest.php index a6ab9ed7b..194a6cca7 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/ScopeFilterTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/ScopeFilterTest.php @@ -22,10 +22,12 @@ use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Export\Filter\ScopeFilter; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Test\Export\AbstractFilterTest; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class ScopeFilterTest extends AbstractFilterTest @@ -52,38 +54,33 @@ final class ScopeFilterTest extends AbstractFilterTest public function getFormData(): array { + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); $array = $em->createQueryBuilder() ->from(Scope::class, 's') ->select('s') ->getQuery() + ->setMaxResults(1) ->getResult(); - $data = []; - - foreach ($array as $a) { - $data[] = [ - 'scope' => $a, - ]; - } - - return $data; + return [ + [ + 'scope' => new ArrayCollection($array), + ], + ]; } public function getQueryBuilders(): array { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() ->select('cal.id') - ->from(Calendar::class, 'cal') - ->join('cal.user', 'caluser'), + ->from(Calendar::class, 'cal'), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php b/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php index 9195d5eac..0d1202b67 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php @@ -24,26 +24,29 @@ use Chill\CalendarBundle\Form\CalendarType; use Chill\CalendarBundle\Form\DataTransformer\IdToCalendarRangeDataTransformer; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Form\DataMapper\PrivateCommentDataMapper; use Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer; use Chill\MainBundle\Form\DataTransformer\IdToUserDataTransformer; use Chill\MainBundle\Form\DataTransformer\IdToUsersDataTransformer; use Chill\MainBundle\Form\Type\CommentType; +use Chill\MainBundle\Form\Type\PrivateCommentType; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Form\DataTransformer\PersonsToIdDataTransformer; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Form\DataTransformer\ThirdPartiesToIdDataTransformer; -use DateTimeImmutable; +use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Doctrine\Common\Collections\Collection; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use ReflectionProperty; use Symfony\Component\Form\PreloadedExtension; use Symfony\Component\Form\Test\TypeTestCase; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Security; /** * @internal + * * @coversNothing */ final class CalendarTypeTest extends TypeTestCase @@ -70,7 +73,7 @@ final class CalendarTypeTest extends TypeTestCase $this->idToUserDataTransformer = $this->buildSingleToIdDataTransformer(IdToUserDataTransformer::class, User::class); $this->idToUsersDataTransformer = $this->buildMultiToIdDataTransformer(IdToUsersDataTransformer::class, User::class); $this->idToLocationDataTransformer = $this->buildSingleToIdDataTransformer(IdToLocationDataTransformer::class, Location::class); - $this->partiesToIdDataTransformer = $this->buildMultiToIdDataTransformer(ThirdPartiesToIdDataTransformer::class, ThirdParty::class); + $this->partiesToIdDataTransformer = $this->buildMultiToIdDataTransformerThirdParties(); $this->calendarRangeDataTransformer = $this->buildSingleToIdDataTransformer(IdToCalendarRangeDataTransformer::class, CalendarRange::class); $tokenStorage = $this->prophesize(TokenStorageInterface::class); $token = $this->prophesize(TokenInterface::class); @@ -86,9 +89,9 @@ final class CalendarTypeTest extends TypeTestCase $formData = [ 'mainUser' => '1', 'users' => '2,3', - 'professionnals' => '4,5', - 'startDate' => '2022-05-05 14:00:00', - 'endDate' => '2022-05-05 14:30:00', + 'professionnals' => '646', + 'startDate' => '2022-05-05T14:00:00+0200', + 'endDate' => '2022-05-05T14:30:00+0200', 'persons' => '7', 'calendarRange' => '8', 'location' => '9', @@ -97,9 +100,9 @@ final class CalendarTypeTest extends TypeTestCase $calendar = new Calendar(); $calendar->setMainUser(new class () extends User { - public function getId() + public function getId(): ?int { - return '1'; + return 1; } }); @@ -108,8 +111,8 @@ final class CalendarTypeTest extends TypeTestCase $form->submit($formData); $this->assertTrue($form->isSynchronized()); - $this->assertEquals(DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-05-05 14:00:00'), $calendar->getStartDate()); - $this->assertEquals(DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-05-05 14:30:00'), $calendar->getEndDate()); + $this->assertEquals(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-05-05 14:00:00'), $calendar->getStartDate()); + $this->assertEquals(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-05-05 14:30:00'), $calendar->getEndDate()); $this->assertEquals(7, $calendar->getPersons()->first()->getId()); $this->assertEquals(8, $calendar->getCalendarRange()->getId()); $this->assertEquals(9, $calendar->getLocation()->getId()); @@ -120,8 +123,6 @@ final class CalendarTypeTest extends TypeTestCase protected function getExtensions() { - $parents = parent::getExtensions(); - $calendarType = new CalendarType( $this->personsToIdDataTransformer, $this->idToUserDataTransformer, @@ -132,12 +133,27 @@ final class CalendarTypeTest extends TypeTestCase ); $commentType = new CommentType($this->tokenStorage); + $user = $this->prophesize(User::class); + $user->getId()->willReturn(1); + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn($user->reveal()); + $privateCommentDataMapper = new PrivateCommentDataMapper($security->reveal()); + $privateCommentType = new PrivateCommentType($privateCommentDataMapper); + return array_merge( parent::getExtensions(), - [new PreloadedExtension([$calendarType, $commentType], [])] + [new PreloadedExtension([$calendarType, $commentType, $privateCommentType], [])] ); } + private function buildMultiToIdDataTransformerThirdParties(): ThirdPartiesToIdDataTransformer + { + $repository = $this->prophesize(ThirdPartyRepository::class); + $repository->findOneBy(Argument::type('array'))->willReturn(new ThirdParty()); + + return new ThirdPartiesToIdDataTransformer($repository->reveal()); + } + private function buildMultiToIdDataTransformer( string $classTransformer, string $objClass @@ -164,13 +180,13 @@ final class CalendarTypeTest extends TypeTestCase return array_map( static function ($id) use ($objClass) { $obj = new $objClass(); - $reflectionProperty = new ReflectionProperty($objClass, 'id'); + $reflectionProperty = new \ReflectionProperty($objClass, 'id'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($obj, (int) $id); return $obj; }, - explode(',', $args[0]) + explode(',', (string) $args[0]) ); }); @@ -192,7 +208,7 @@ final class CalendarTypeTest extends TypeTestCase return null; } $obj = new $class(); - $reflectionProperty = new ReflectionProperty($class, 'id'); + $reflectionProperty = new \ReflectionProperty($class, 'id'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($obj, (int) $args[0]); diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/AddressConverterTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/AddressConverterTest.php index 827e22381..eca929875 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/AddressConverterTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/AddressConverterTest.php @@ -27,10 +27,10 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\Templating\EngineInterface; /** * @internal + * * @coversNothing */ final class AddressConverterTest extends TestCase @@ -61,7 +61,7 @@ final class AddressConverterTest extends TestCase private function buildAddressConverter(): AddressConverter { - $engine = $this->prophesize(EngineInterface::class); + $engine = $this->prophesize(\Twig\Environment::class); $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); $translatableStringHelper->localize(Argument::type('array'))->will(static fn ($args): string => ($args[0] ?? ['fr' => 'not provided'])['fr'] ?? 'not provided'); diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarRangeSyncerTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarRangeSyncerTest.php index 92280f6c1..9f7df0c2c 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarRangeSyncerTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarRangeSyncerTest.php @@ -22,7 +22,6 @@ use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\CalendarRange; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteToLocalSync\CalendarRangeSyncer; use Chill\MainBundle\Entity\User; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -33,6 +32,7 @@ use Symfony\Component\HttpClient\Response\MockResponse; /** * @internal + * * @coversNothing */ final class CalendarRangeSyncerTest extends TestCase @@ -228,8 +228,8 @@ final class CalendarRangeSyncerTest extends TestCase $calendarRange = new CalendarRange(); $calendarRange ->setUser($user = new User()) - ->setStartDate(new DateTimeImmutable('2020-01-01 15:00:00')) - ->setEndDate(new DateTimeImmutable('2020-01-01 15:30:00')) + ->setStartDate(new \DateTimeImmutable('2020-01-01 15:00:00')) + ->setEndDate(new \DateTimeImmutable('2020-01-01 15:30:00')) ->addRemoteAttributes([ 'lastModifiedDateTime' => 0, 'changeKey' => 'abc', @@ -244,11 +244,11 @@ final class CalendarRangeSyncerTest extends TestCase $this->assertStringContainsString( '2022-06-10T15:30:00', - $calendarRange->getStartDate()->format(DateTimeImmutable::ATOM) + $calendarRange->getStartDate()->format(\DateTimeImmutable::ATOM) ); $this->assertStringContainsString( '2022-06-10T17:30:00', - $calendarRange->getEndDate()->format(DateTimeImmutable::ATOM) + $calendarRange->getEndDate()->format(\DateTimeImmutable::ATOM) ); $this->assertTrue($calendarRange->preventEnqueueChanges); } diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarSyncerTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarSyncerTest.php index 6ccd7a35e..e6196aead 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarSyncerTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarSyncerTest.php @@ -24,8 +24,6 @@ use Chill\CalendarBundle\Entity\Invite; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteToLocalSync\CalendarSyncer; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\UserRepositoryInterface; -use DateTimeImmutable; -use DateTimeZone; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -35,6 +33,7 @@ use Symfony\Component\HttpClient\Response\MockResponse; /** * @internal + * * @coversNothing */ final class CalendarSyncerTest extends TestCase @@ -357,20 +356,6 @@ final class CalendarSyncerTest extends TestCase } JSON; - protected function setUp(): void - { - parent::setUp(); - - // all tests should run when timezone = +02:00 - $brussels = new DateTimeZone('Europe/Brussels'); - - if (7200 === $brussels->getOffset(new DateTimeImmutable())) { - date_default_timezone_set('Europe/Brussels'); - } else { - date_default_timezone_set('Europe/Moscow'); - } - } - public function testHandleAttendeesConfirmingCalendar(): void { $machineHttpClient = new MockHttpClient([ @@ -403,8 +388,8 @@ final class CalendarSyncerTest extends TestCase $calendar = new Calendar(); $calendar ->setMainUser($user = new User()) - ->setStartDate(new DateTimeImmutable('2022-06-11 14:30:00')) - ->setEndDate(new DateTimeImmutable('2022-06-11 15:30:00')) + ->setStartDate(new \DateTimeImmutable('2022-06-11 14:30:00')) + ->setEndDate(new \DateTimeImmutable('2022-06-11 15:30:00')) ->addUser($userA) ->addUser($userB) ->setCalendarRange(new CalendarRange()) @@ -480,13 +465,14 @@ final class CalendarSyncerTest extends TestCase $calendar = new Calendar(); $calendar ->setMainUser($user = new User()) - ->setStartDate(new DateTimeImmutable('2020-01-01 10:00:00')) - ->setEndDate(new DateTimeImmutable('2020-01-01 12:00:00')) + ->setStartDate(new \DateTimeImmutable('2020-01-01 10:00:00')) + ->setEndDate(new \DateTimeImmutable('2020-01-01 12:00:00')) ->setCalendarRange(new CalendarRange()) ->addRemoteAttributes([ 'lastModifiedDateTime' => 0, 'changeKey' => 'abcd', ]); + $previousVersion = $calendar->getDateTimeVersion(); $notification = json_decode(self::NOTIF_UPDATE, true); $calendarSyncer->handleCalendarSync( @@ -497,14 +483,14 @@ final class CalendarSyncerTest extends TestCase $this->assertStringContainsString( '2022-06-10T15:30:00', - $calendar->getStartDate()->format(DateTimeImmutable::ATOM) + $calendar->getStartDate()->format(\DateTimeImmutable::ATOM) ); $this->assertStringContainsString( '2022-06-10T17:30:00', - $calendar->getEndDate()->format(DateTimeImmutable::ATOM) + $calendar->getEndDate()->format(\DateTimeImmutable::ATOM) ); $this->assertTrue($calendar->preventEnqueueChanges); - $this->assertEquals(Calendar::STATUS_MOVED, $calendar->getStatus()); + $this->assertGreaterThan($previousVersion, $calendar->getDateTimeVersion()); } public function testHandleNotMovedCalendar(): void @@ -523,8 +509,8 @@ final class CalendarSyncerTest extends TestCase $calendar = new Calendar(); $calendar ->setMainUser($user = new User()) - ->setStartDate(new DateTimeImmutable('2022-06-10 15:30:00')) - ->setEndDate(new DateTimeImmutable('2022-06-10 17:30:00')) + ->setStartDate(new \DateTimeImmutable('2022-06-10 15:30:00')) + ->setEndDate(new \DateTimeImmutable('2022-06-10 17:30:00')) ->setCalendarRange(new CalendarRange()) ->addRemoteAttributes([ 'lastModifiedDateTime' => 0, @@ -540,11 +526,11 @@ final class CalendarSyncerTest extends TestCase $this->assertStringContainsString( '2022-06-10T15:30:00', - $calendar->getStartDate()->format(DateTimeImmutable::ATOM) + $calendar->getStartDate()->format(\DateTimeImmutable::ATOM) ); $this->assertStringContainsString( '2022-06-10T17:30:00', - $calendar->getEndDate()->format(DateTimeImmutable::ATOM) + $calendar->getEndDate()->format(\DateTimeImmutable::ATOM) ); $this->assertTrue($calendar->preventEnqueueChanges); $this->assertEquals(Calendar::STATUS_VALID, $calendar->getStatus()); @@ -567,8 +553,8 @@ final class CalendarSyncerTest extends TestCase $calendar = new Calendar(); $calendar ->setMainUser($user = new User()) - ->setStartDate(new DateTimeImmutable('2020-01-01 10:00:00')) - ->setEndDate(new DateTimeImmutable('2020-01-01 12:00:00')) + ->setStartDate(new \DateTimeImmutable('2020-01-01 10:00:00')) + ->setEndDate(new \DateTimeImmutable('2020-01-01 12:00:00')) ->setCalendarRange(new CalendarRange()) ->addRemoteAttributes([ 'lastModifiedDateTime' => 0, @@ -584,11 +570,11 @@ final class CalendarSyncerTest extends TestCase $this->assertStringContainsString( '2020-01-01T10:00:00', - $calendar->getStartDate()->format(DateTimeImmutable::ATOM) + $calendar->getStartDate()->format(\DateTimeImmutable::ATOM) ); $this->assertStringContainsString( '2020-01-01T12:00:00', - $calendar->getEndDate()->format(DateTimeImmutable::ATOM) + $calendar->getEndDate()->format(\DateTimeImmutable::ATOM) ); $this->assertEquals(Calendar::STATUS_VALID, $calendar->getStatus()); diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/LocationConverterTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/LocationConverterTest.php index 604b707c0..55dfe427c 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/LocationConverterTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/LocationConverterTest.php @@ -30,6 +30,7 @@ use Prophecy\PhpUnit\ProphecyTrait; /** * @internal + * * @coversNothing */ final class LocationConverterTest extends TestCase diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php index 089477fda..4307f32a3 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php @@ -22,6 +22,7 @@ use Symfony\Component\HttpClient\Response\MockResponse; /** * @internal + * * @coversNothing */ class MSUserAbsenceReaderTest extends TestCase @@ -55,7 +56,7 @@ class MSUserAbsenceReaderTest extends TestCase $absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock); - self::assertNull($absenceReader->isUserAbsent($user), "when no user found, absence should be null"); + self::assertNull($absenceReader->isUserAbsent($user), 'when no user found, absence should be null'); } public function provideDataTestUserAbsence(): iterable @@ -81,7 +82,7 @@ class MSUserAbsenceReaderTest extends TestCase } JSON, false, - "User is present" + 'User is present', ]; yield [ @@ -103,7 +104,7 @@ class MSUserAbsenceReaderTest extends TestCase } JSON, true, - 'User is absent with absence scheduled, we are within this period' + 'User is absent with absence scheduled, we are within this period', ]; yield [ @@ -125,7 +126,7 @@ class MSUserAbsenceReaderTest extends TestCase } JSON, false, - 'User is present: absence is scheduled for later' + 'User is present: absence is scheduled for later', ]; yield [ @@ -147,7 +148,7 @@ class MSUserAbsenceReaderTest extends TestCase } JSON, false, - 'User is present: absence is past' + 'User is present: absence is past', ]; yield [ @@ -169,8 +170,7 @@ class MSUserAbsenceReaderTest extends TestCase } JSON, true, - "User is absent: absence is always enabled" + 'User is absent: absence is always enabled', ]; } - } diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSyncTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSyncTest.php index 1b0f1e416..244c7e468 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSyncTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSyncTest.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Tests\RemoteCalendar\Connector\MSGraph; -use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReader; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReaderInterface; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync; use Chill\MainBundle\Entity\User; @@ -22,6 +21,7 @@ use Symfony\Component\Clock\MockClock; /** * @internal + * * @coversNothing */ class MSUserAbsenceSyncTest extends TestCase @@ -48,21 +48,21 @@ class MSUserAbsenceSyncTest extends TestCase public function provideDataTestSyncUserAbsence(): iterable { - yield [new User(), false, false, null, "user present remains present"]; - yield [new User(), true, true, new \DateTimeImmutable('2023-07-01T12:00:00'), "user present becomes absent"]; + yield [new User(), false, false, null, 'user present remains present']; + yield [new User(), true, true, new \DateTimeImmutable('2023-07-01T12:00:00'), 'user present becomes absent']; $user = new User(); - $user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00")); - yield [$user, true, true, $abs, "user absent remains absent"]; + $user->setAbsenceStart($abs = new \DateTimeImmutable('2023-07-01T12:00:00')); + yield [$user, true, true, $abs, 'user absent remains absent']; $user = new User(); - $user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00")); - yield [$user, false, false, null, "user absent becomes present"]; + $user->setAbsenceStart($abs = new \DateTimeImmutable('2023-07-01T12:00:00')); + yield [$user, false, false, null, 'user absent becomes present']; - yield [new User(), null, false, null, "user not syncable: presence do not change"]; + yield [new User(), null, false, null, 'user not syncable: presence do not change']; $user = new User(); - $user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00")); - yield [$user, null, true, $abs, "user not syncable: absence do not change"]; + $user->setAbsenceStart($abs = new \DateTimeImmutable('2023-07-01T12:00:00')); + yield [$user, null, true, $abs, 'user not syncable: absence do not change']; } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Repository/CalendarACLAwareRepositoryTest.php b/src/Bundle/ChillCalendarBundle/Tests/Repository/CalendarACLAwareRepositoryTest.php index 0fb4a662b..8d3101c5b 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Repository/CalendarACLAwareRepositoryTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Repository/CalendarACLAwareRepositoryTest.php @@ -15,13 +15,13 @@ use Chill\CalendarBundle\Repository\CalendarACLAwareRepository; use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper; use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; /** * @internal + * * @coversNothing */ final class CalendarACLAwareRepositoryTest extends KernelTestCase @@ -51,7 +51,7 @@ final class CalendarACLAwareRepositoryTest extends KernelTestCase $this->entityManager ); - $count = $calendarRepository->countByPerson($person, new DateTimeImmutable('yesterday'), new DateTimeImmutable('tomorrow')); + $count = $calendarRepository->countByPerson($person, new \DateTimeImmutable('yesterday'), new \DateTimeImmutable('tomorrow')); $this->assertIsInt($count); } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Serializer/Normalizer/CalendarNormalizerTest.php b/src/Bundle/ChillCalendarBundle/Tests/Serializer/Normalizer/CalendarNormalizerTest.php index 2a0975206..03ba7f164 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Serializer/Normalizer/CalendarNormalizerTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Serializer/Normalizer/CalendarNormalizerTest.php @@ -16,12 +16,12 @@ use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\Person; use Chill\ThirdPartyBundle\Entity\ThirdParty; -use DateTimeImmutable; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * @internal + * * @coversNothing */ final class CalendarNormalizerTest extends KernelTestCase @@ -41,8 +41,8 @@ final class CalendarNormalizerTest extends KernelTestCase ->setComment( $comment = new CommentEmbeddable() ) - ->setStartDate(DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, '2020-10-15T15:00:00+0000')) - ->setEndDate(DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, '2020-15-15T15:30:00+0000')) + ->setStartDate(\DateTimeImmutable::createFromFormat(\DateTimeImmutable::ATOM, '2020-10-15T15:00:00+0000')) + ->setEndDate(\DateTimeImmutable::createFromFormat(\DateTimeImmutable::ATOM, '2020-15-15T15:30:00+0000')) ->addPerson(new Person()) ->addPerson(new Person()) ->addUser(new User()) diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php index cbb4ea3af..4a4b4f7d4 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php @@ -19,8 +19,10 @@ use Chill\DocGeneratorBundle\Service\Context\BaseContextData; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Templating\Entity\PersonRender; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; @@ -31,11 +33,10 @@ use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use function array_key_exists; -use function count; /** * @internal + * * @coversNothing */ final class CalendarContextTest extends TestCase @@ -83,13 +84,13 @@ final class CalendarContextTest extends TestCase // so, we expect the call to be twice for each method $formBuilder->add('thirdParty', EntityType::class, Argument::type('array')) ->should(static function ($calls, $object, $method) use ($tp1, $tp2) { - if (2 !== count($calls)) { - throw new FailedPredictionException(sprintf('the $builder->add should be called exactly 2, %d receivved', count($calls))); + if (2 !== \count($calls)) { + throw new FailedPredictionException(sprintf('the $builder->add should be called exactly 2, %d receivved', \count($calls))); } $opts = $calls[0]->getArguments()[2]; - if (!array_key_exists('label', $opts)) { + if (!\array_key_exists('label', $opts)) { throw new FailedPredictionException('the $builder->add should have a label key'); } @@ -103,13 +104,13 @@ final class CalendarContextTest extends TestCase }); $formBuilder->add('mainPerson', EntityType::class, Argument::type('array')) ->should(static function ($calls, $object, $method) use ($p1) { - if (2 !== count($calls)) { - throw new FailedPredictionException(sprintf('the $builder->add should be called exactly 2, %d receivved', count($calls))); + if (2 !== \count($calls)) { + throw new FailedPredictionException(sprintf('the $builder->add should be called exactly 2, %d receivved', \count($calls))); } $opts = $calls[0]->getArguments()[2]; - if (!array_key_exists('label', $opts)) { + if (!\array_key_exists('label', $opts)) { throw new FailedPredictionException('the $builder->add should have a label key'); } @@ -176,7 +177,7 @@ final class CalendarContextTest extends TestCase $em = $this->prophesize(EntityManagerInterface::class); $em->persist(Argument::type(CalendarDoc::class))->should( static function ($calls, $object, $method) use ($storedObject) { - if (1 !== count($calls)) { + if (1 !== \count($calls)) { throw new FailedPredictionException('the persist method should be called once'); } @@ -187,7 +188,7 @@ final class CalendarContextTest extends TestCase throw new FailedPredictionException('the stored object is not correct'); } - if ($calendarDoc->getStoredObject()->getTitle() !== 'blabla') { + if ('blabla' !== $calendarDoc->getStoredObject()->getTitle()) { throw new FailedPredictionException('the doc title should be the one provided'); } @@ -210,9 +211,15 @@ final class CalendarContextTest extends TestCase $personRender = $this->prophesize(PersonRender::class); $personRender->renderString(Argument::type(Person::class), [])->willReturn('person name'); + $personRepository = $this->prophesize(PersonRepository::class); + $personRepository->find(Argument::type('int'))->willReturn(new Person()); + $thirdPartyRender = $this->prophesize(ThirdPartyRender::class); $thirdPartyRender->renderString(Argument::type(ThirdParty::class), [])->willReturn('third party name'); + $thirdPartyRepository = $this->prophesize(ThirdPartyRepository::class); + $thirdPartyRepository->find(Argument::type('int'))->willReturn(new ThirdParty()); + $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); $translatableStringHelper->localize(Argument::type('array'))->willReturn('blabla'); @@ -229,7 +236,9 @@ final class CalendarContextTest extends TestCase $entityManager, $normalizer, $personRender->reveal(), + $personRepository->reveal(), $thirdPartyRender->reveal(), + $thirdPartyRepository->reveal(), $translatableStringHelper->reveal() ); } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/BulkCalendarShortMessageSenderTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/BulkCalendarShortMessageSenderTest.php index 0c190f51d..308c17237 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/BulkCalendarShortMessageSenderTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/BulkCalendarShortMessageSenderTest.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Tests\Service\ShortMessageNotification; -use ArrayIterator; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Service\ShortMessageNotification\BulkCalendarShortMessageSender; use Chill\CalendarBundle\Service\ShortMessageNotification\CalendarForShortMessageProvider; @@ -27,20 +26,18 @@ use Chill\MainBundle\Entity\User; use Chill\MainBundle\Service\ShortMessage\ShortMessage; use Chill\MainBundle\Test\PrepareUserTrait; use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper; -use DateInterval; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use libphonenumber\PhoneNumberUtil; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Psr\Log\NullLogger; -use stdClass; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; /** * @internal + * * @coversNothing */ final class BulkCalendarShortMessageSenderTest extends KernelTestCase @@ -79,26 +76,26 @@ final class BulkCalendarShortMessageSenderTest extends KernelTestCase $calendar ->addPerson($this->getRandomPerson($em)) ->setMainUser($user = $this->prepareUser([])) - ->setStartDate(new DateTimeImmutable('now')) - ->setEndDate($calendar->getStartDate()->add(new DateInterval('PT30M'))) + ->setStartDate(new \DateTimeImmutable('now')) + ->setEndDate($calendar->getStartDate()->add(new \DateInterval('PT30M'))) ->setSendSMS(true); $user->setUsername(uniqid()); - $user->setEmail(uniqid() . '@gmail.com'); + $user->setEmail(uniqid().'@gmail.com'); $calendar->getPersons()->first()->setAcceptSMS(true); // hack to prevent side effect with messages $calendar->preventEnqueueChanges = true; $em->persist($user); - //$this->toDelete[] = [User::class, $user->getId()]; + // $this->toDelete[] = [User::class, $user->getId()]; $em->persist($calendar); - //$this->toDelete[] = [Calendar::class, $calendar->getId()]; + // $this->toDelete[] = [Calendar::class, $calendar->getId()]; $em->flush(); $provider = $this->prophesize(CalendarForShortMessageProvider::class); - $provider->getCalendars(Argument::type(DateTimeImmutable::class)) - ->willReturn(new ArrayIterator([$calendar])); + $provider->getCalendars(Argument::type(\DateTimeImmutable::class)) + ->willReturn(new \ArrayIterator([$calendar])); $messageBuilder = $this->prophesize(ShortMessageForCalendarBuilderInterface::class); $messageBuilder->buildMessageForCalendar(Argument::type(Calendar::class)) @@ -114,7 +111,7 @@ final class BulkCalendarShortMessageSenderTest extends KernelTestCase $bus = $this->prophesize(MessageBusInterface::class); $bus->dispatch(Argument::type(ShortMessage::class)) - ->willReturn(new Envelope(new stdClass())) + ->willReturn(new Envelope(new \stdClass())) ->shouldBeCalledTimes(1); $bulk = new BulkCalendarShortMessageSender( diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/CalendarForShortMessageProviderTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/CalendarForShortMessageProviderTest.php index 34867d8bd..47af7d68e 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/CalendarForShortMessageProviderTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/CalendarForShortMessageProviderTest.php @@ -23,15 +23,14 @@ use Chill\CalendarBundle\Repository\CalendarRepository; use Chill\CalendarBundle\Service\ShortMessageNotification\CalendarForShortMessageProvider; use Chill\CalendarBundle\Service\ShortMessageNotification\DefaultRangeGenerator; use Chill\CalendarBundle\Service\ShortMessageNotification\RangeGeneratorInterface; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use function count; /** * @internal + * * @coversNothing */ final class CalendarForShortMessageProviderTest extends TestCase @@ -42,13 +41,13 @@ final class CalendarForShortMessageProviderTest extends TestCase { $calendarRepository = $this->prophesize(CalendarRepository::class); $calendarRepository->findByNotificationAvailable( - Argument::type(DateTimeImmutable::class), - Argument::type(DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), Argument::type('int'), Argument::exact(0) )->shouldBeCalledTimes(0); $rangeGenerator = $this->prophesize(RangeGeneratorInterface::class); - $rangeGenerator->generateRange(Argument::type(DateTimeImmutable::class))->willReturn(null); + $rangeGenerator->generateRange(Argument::type(\DateTimeImmutable::class))->willReturn(null); $em = $this->prophesize(EntityManagerInterface::class); $em->clear()->shouldNotBeCalled(); @@ -59,23 +58,23 @@ final class CalendarForShortMessageProviderTest extends TestCase $rangeGenerator->reveal() ); - $calendars = iterator_to_array($provider->getCalendars(new DateTimeImmutable('now'))); + $calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now'))); - $this->assertEquals(0, count($calendars)); + $this->assertEquals(0, \count($calendars)); } public function testGetCalendars() { $calendarRepository = $this->prophesize(CalendarRepository::class); $calendarRepository->findByNotificationAvailable( - Argument::type(DateTimeImmutable::class), - Argument::type(DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), Argument::type('int'), Argument::exact(0) )->will(static fn ($args) => array_fill(0, $args[2], new Calendar()))->shouldBeCalledTimes(1); $calendarRepository->findByNotificationAvailable( - Argument::type(DateTimeImmutable::class), - Argument::type(DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), Argument::type('int'), Argument::not(0) )->will(static fn ($args) => array_fill(0, $args[2] - 1, new Calendar()))->shouldBeCalledTimes(1); @@ -89,10 +88,10 @@ final class CalendarForShortMessageProviderTest extends TestCase new DefaultRangeGenerator() ); - $calendars = iterator_to_array($provider->getCalendars(new DateTimeImmutable('now'))); + $calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now'))); - $this->assertGreaterThan(1, count($calendars)); - $this->assertLessThan(100, count($calendars)); + $this->assertGreaterThan(1, \count($calendars)); + $this->assertLessThan(100, \count($calendars)); $this->assertContainsOnly(Calendar::class, $calendars); } @@ -100,14 +99,14 @@ final class CalendarForShortMessageProviderTest extends TestCase { $calendarRepository = $this->prophesize(CalendarRepository::class); $calendarRepository->findByNotificationAvailable( - Argument::type(DateTimeImmutable::class), - Argument::type(DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), Argument::type('int'), Argument::exact(0) )->will(static fn ($args) => array_fill(0, 1, new Calendar()))->shouldBeCalledTimes(1); $calendarRepository->findByNotificationAvailable( - Argument::type(DateTimeImmutable::class), - Argument::type(DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), + Argument::type(\DateTimeImmutable::class), Argument::type('int'), Argument::not(0) )->will(static fn ($args) => [])->shouldBeCalledTimes(1); @@ -121,9 +120,9 @@ final class CalendarForShortMessageProviderTest extends TestCase new DefaultRangeGenerator() ); - $calendars = iterator_to_array($provider->getCalendars(new DateTimeImmutable('now'))); + $calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now'))); - $this->assertEquals(1, count($calendars)); + $this->assertEquals(1, \count($calendars)); $this->assertContainsOnly(Calendar::class, $calendars); } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php index de2b97963..552595414 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php @@ -19,12 +19,11 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Tests\Service\ShortMessageNotification; use Chill\CalendarBundle\Service\ShortMessageNotification\DefaultRangeGenerator; -use DateTimeImmutable; -use Iterator; use PHPUnit\Framework\TestCase; /** * @internal + * * @coversNothing */ final class DefaultRangeGeneratorTest extends TestCase @@ -36,46 +35,46 @@ final class DefaultRangeGeneratorTest extends TestCase * * Jeudi => envoi des rdv du samedi et dimanche * * Vendredi => Envoi des rdv du lundi. */ - public function generateData(): Iterator + public function generateData(): \Iterator { yield [ - new DateTimeImmutable('2022-06-13 10:45:00'), - new DateTimeImmutable('2022-06-14 00:00:00'), - new DateTimeImmutable('2022-06-16 00:00:00'), + new \DateTimeImmutable('2022-06-13 10:45:00'), + new \DateTimeImmutable('2022-06-14 00:00:00'), + new \DateTimeImmutable('2022-06-16 00:00:00'), ]; yield [ - new DateTimeImmutable('2022-06-14 15:45:00'), - new DateTimeImmutable('2022-06-16 00:00:00'), - new DateTimeImmutable('2022-06-17 00:00:00'), + new \DateTimeImmutable('2022-06-14 15:45:00'), + new \DateTimeImmutable('2022-06-16 00:00:00'), + new \DateTimeImmutable('2022-06-17 00:00:00'), ]; yield [ - new DateTimeImmutable('2022-06-15 13:45:18'), - new DateTimeImmutable('2022-06-17 00:00:00'), - new DateTimeImmutable('2022-06-18 00:00:00'), + new \DateTimeImmutable('2022-06-15 13:45:18'), + new \DateTimeImmutable('2022-06-17 00:00:00'), + new \DateTimeImmutable('2022-06-18 00:00:00'), ]; yield [ - new DateTimeImmutable('2022-06-16 01:30:55'), - new DateTimeImmutable('2022-06-18 00:00:00'), - new DateTimeImmutable('2022-06-20 00:00:00'), + new \DateTimeImmutable('2022-06-16 01:30:55'), + new \DateTimeImmutable('2022-06-18 00:00:00'), + new \DateTimeImmutable('2022-06-20 00:00:00'), ]; yield [ - new DateTimeImmutable('2022-06-17 21:30:55'), - new DateTimeImmutable('2022-06-20 00:00:00'), - new DateTimeImmutable('2022-06-21 00:00:00'), + new \DateTimeImmutable('2022-06-17 21:30:55'), + new \DateTimeImmutable('2022-06-20 00:00:00'), + new \DateTimeImmutable('2022-06-21 00:00:00'), ]; yield [ - new DateTimeImmutable('2022-06-18 21:30:55'), + new \DateTimeImmutable('2022-06-18 21:30:55'), null, null, ]; yield [ - new DateTimeImmutable('2022-06-19 21:30:55'), + new \DateTimeImmutable('2022-06-19 21:30:55'), null, null, ]; @@ -84,7 +83,7 @@ final class DefaultRangeGeneratorTest extends TestCase /** * @dataProvider generateData */ - public function testGenerateRange(DateTimeImmutable $date, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate) + public function testGenerateRange(\DateTimeImmutable $date, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate) { $generator = new DefaultRangeGenerator(); @@ -94,8 +93,8 @@ final class DefaultRangeGeneratorTest extends TestCase $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)); + $this->assertEquals($startDate->format(\DateTimeImmutable::ATOM), $actualStartDate->format(\DateTimeImmutable::ATOM)); + $this->assertEquals($endDate->format(\DateTimeImmutable::ATOM), $actualEndDate->format(\DateTimeImmutable::ATOM)); } } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilderTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilderTest.php index 6d42540cc..222d3a451 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilderTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilderTest.php @@ -23,17 +23,15 @@ use Chill\CalendarBundle\Service\ShortMessageNotification\DefaultShortMessageFor use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\Person; -use DateInterval; -use DateTimeImmutable; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\Templating\EngineInterface; /** * @internal + * * @coversNothing */ final class DefaultShortMessageForCalendarBuilderTest extends TestCase @@ -51,8 +49,8 @@ final class DefaultShortMessageForCalendarBuilderTest extends TestCase { $calendar = new Calendar(); $calendar - ->setStartDate(new DateTimeImmutable('now')) - ->setEndDate($calendar->getStartDate()->add(new DateInterval('PT30M'))) + ->setStartDate(new \DateTimeImmutable('now')) + ->setEndDate($calendar->getStartDate()->add(new \DateInterval('PT30M'))) ->setMainUser($user = new User()) ->addPerson($person = new Person()) ->setSendSMS(false); @@ -64,7 +62,7 @@ final class DefaultShortMessageForCalendarBuilderTest extends TestCase ->setMobilenumber($this->phoneNumberUtil->parse('+32470123456', 'BE')) ->setAcceptSMS(false); - $engine = $this->prophesize(EngineInterface::class); + $engine = $this->prophesize(\Twig\Environment::class); $engine->render(Argument::exact('@ChillCalendar/CalendarShortMessage/short_message.txt.twig'), Argument::withKey('calendar')) ->willReturn('message content') ->shouldBeCalledTimes(1); diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index 95faec742..cfb2fe057 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -100,24 +100,32 @@ Get the sum of appointment durations according to various filters: Calcule la so 'Filtered by agent: only %agents%': "Filtré par agents: uniquement %agents%" Filter calendars by agent: Filtrer les rendez-vous par agents -Filter calendars by agent job: Filtrer les rendez-vous par métiers des agents -'Filtered by agent job: only %jobs%': 'Filtré par métiers des agents: uniquement les %jobs%' -Filter calendars by agent scope: Filtrer les rendez-vous par services des agents -'Filtered by agent scope: only %scopes%': 'Filtré par services des agents: uniquement les services %scopes%' Filter calendars between certain dates: Filtrer les rendez-vous par date du rendez-vous 'Filtered by calendars between %dateFrom% and %dateTo%': 'Filtré par rendez-vous entre %dateFrom% et %dateTo%' 'Filtered by calendar range: only %calendarRange%': 'Filtré par rendez-vous par plage de disponibilité: uniquement les %calendarRange%' Filter by calendar range: Filtrer par rendez-vous dans une plage de disponibilité ou non Group calendars by agent: Grouper les rendez-vous par agent -Group calendars by agent job: Grouper les rendez-vous par métier de l'agent -Group calendars by agent scope: Grouper les rendez-vous par service de l'agent Group calendars by location type: Grouper les rendez-vous par type de localisation Group calendars by location: Grouper les rendez-vous par lieu de rendez-vous Group calendars by cancel reason: Grouper les rendez-vous par motif d'annulation Group calendars by month and year: Grouper les rendez-vous par mois et année Group calendars by urgency: Grouper les rendez-vous par urgent ou non +export: + aggregator.calendar: + agent_job: + Group calendars by agent job: Grouper les rendez-vous par métier de l'agent + agent_scope: + Group calendars by agent scope: Grouper les rendez-vous par service de l'agent + filter.calendar: + agent_job: + Filter calendars by agent job: Filtrer les rendez-vous par métiers des agents (utilisateurs principaux) + 'Filtered by agent job: only %jobs%': 'Filtré par métiers des agents (utilisateurs principaux): uniquement les %jobs%' + agent_scope: + Filter calendars by agent scope: Filtrer les rendez-vous par services des agents (utilisateurs principaux) + 'Filtered by agent scope: only %scopes%': 'Filtré par services des agents (utilisateurs principaux): uniquement les services %scopes%' + Scope: Service Job: Métier Location type: Type de localisation diff --git a/src/Bundle/ChillCustomFieldsBundle/ChillCustomFieldsBundle.php b/src/Bundle/ChillCustomFieldsBundle/ChillCustomFieldsBundle.php index 4167efd2e..5d32ed7e9 100644 --- a/src/Bundle/ChillCustomFieldsBundle/ChillCustomFieldsBundle.php +++ b/src/Bundle/ChillCustomFieldsBundle/ChillCustomFieldsBundle.php @@ -19,6 +19,6 @@ class ChillCustomFieldsBundle extends Bundle public function build(\Symfony\Component\DependencyInjection\ContainerBuilder $container) { parent::build($container); - $container->addCompilerPass(new CustomFieldCompilerPass()); + $container->addCompilerPass(new CustomFieldCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); } } diff --git a/src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php b/src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php index 2a03e90bb..de3f0cf5b 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php +++ b/src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php @@ -15,7 +15,6 @@ use Chill\CustomFieldsBundle\Entity\CustomField; use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup; use Chill\CustomFieldsBundle\Service\CustomFieldProvider; use Doctrine\ORM\EntityManager; -use RuntimeException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; @@ -26,55 +25,26 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser; -use function count; - /** * Class for the command 'chill:custom_fields:populate_group' that * Create custom fields from a yml file. */ class CreateFieldsOnGroupCommand extends Command { - public const ARG_DELETE = 'delete'; + final public const ARG_DELETE = 'delete'; - public const ARG_PATH = 'path'; - - private $availableLanguages; - - /** - * @var CustomFieldProvider - */ - private $customFieldProvider; - - private $customizablesEntities; - - /** - * @var EntityManager - */ - private $entityManager; - - /** - * @var ValidatorInterface - */ - private $validator; + final public const ARG_PATH = 'path'; /** * CreateFieldsOnGroupCommand constructor. - * - * @param $availableLanguages - * @param $customizablesEntities */ public function __construct( - CustomFieldProvider $customFieldProvider, - EntityManager $entityManager, - ValidatorInterface $validator, - $availableLanguages, - $customizablesEntities + private readonly CustomFieldProvider $customFieldProvider, + private readonly EntityManager $entityManager, + private readonly ValidatorInterface $validator, + private $availableLanguages, + private $customizablesEntities ) { - $this->customFieldProvider = $customFieldProvider; - $this->entityManager = $entityManager; - $this->validator = $validator; - $this->availableLanguages = $availableLanguages; - $this->customizablesEntities = $customizablesEntities; parent::__construct(); } @@ -109,22 +79,19 @@ class CreateFieldsOnGroupCommand extends Command } } - /** - * @return int|void|null - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $helper = $this->getHelperSet()->get('question'); $em = $this->entityManager; $customFieldsGroups = $em - ->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class) + ->getRepository(CustomFieldsGroup::class) ->findAll(); - if (count($customFieldsGroups) === 0) { + if (0 === \count($customFieldsGroups)) { $output->writeln('There aren\'t any CustomFieldsGroup recorded' - . ' Please create at least one.'); + .' Please create at least one.'); } $table = new Table($output); @@ -147,7 +114,7 @@ class CreateFieldsOnGroupCommand extends Command } } - throw new RuntimeException('The id does not match an existing CustomFieldsGroup'); + throw new \RuntimeException('The id does not match an existing CustomFieldsGroup'); } ); $customFieldsGroup = $helper->ask($input, $output, $question); @@ -162,6 +129,8 @@ class CreateFieldsOnGroupCommand extends Command ); $fields = $this->_addFields($customFieldsGroup, $fieldsInput, $output); + + return 0; } private function _addFields(CustomFieldsGroup $group, $values, OutputInterface $output) @@ -171,12 +140,11 @@ class CreateFieldsOnGroupCommand extends Command $languages = $this->availableLanguages; foreach ($values['fields'] as $slug => $field) { - //check the cf type exists + // check the cf type exists $cfType = $this->customFieldProvider->getCustomFieldByType($field['type']); if (null === $cfType) { - throw new RuntimeException('the type ' . $field['type'] . ' ' - . 'does not exists'); + throw new \RuntimeException('the type '.$field['type'].' does not exists'); } $cf = new CustomField(); @@ -187,21 +155,21 @@ class CreateFieldsOnGroupCommand extends Command ->setType($field['type']) ->setCustomFieldsGroup($group); - //add to table + // add to table $names = []; foreach ($languages as $lang) { - //todo replace with service to find lang when available + // todo replace with service to find lang when available $names[] = $cf->getName()[$lang] ?? 'Not available in this language'; } if ($this->validator->validate($cf)) { $em->persist($cf); $output->writeln('Adding Custom Field of type ' - . $cf->getType() . "\t with slug " . $cf->getSlug() . - "\t and names : " . implode(', ', $names) . ''); + .$cf->getType()."\t with slug ".$cf->getSlug(). + "\t and names : ".implode(', ', $names).''); } else { - throw new RuntimeException('Error in field ' . $slug); + throw new \RuntimeException('Error in field '.$slug); } } @@ -213,13 +181,13 @@ class CreateFieldsOnGroupCommand extends Command $parser = new Parser(); if (!file_exists($path)) { - throw new RuntimeException('file does not exist'); + throw new \RuntimeException('file does not exist'); } try { $values = $parser->parse(file_get_contents($path)); } catch (ParseException $ex) { - throw new RuntimeException('The yaml file is not valid', 0, $ex); + throw new \RuntimeException('The yaml file is not valid', 0, $ex); } return $values; @@ -229,7 +197,7 @@ class CreateFieldsOnGroupCommand extends Command { $rows = []; $languages = $this->availableLanguages; - //gather entitites and create an array to access them easily + // gather entitites and create an array to access them easily $customizableEntities = []; foreach ($this->customizablesEntities as $entry) { @@ -239,14 +207,14 @@ class CreateFieldsOnGroupCommand extends Command array_walk( $customFieldsGroups, static function (CustomFieldsGroup $customFieldGroup, $key) use ($languages, &$rows, $customizableEntities) { - //set id and entity + // set id and entity $row = [ $customFieldGroup->getId(), $customizableEntities[$customFieldGroup->getEntity()], ]; foreach ($languages as $lang) { - //todo replace with service to find lang when available + // todo replace with service to find lang when available $row[] = $customFieldGroup->getName()[$lang] ?? 'Not available in this language'; } $rows[] = $row; diff --git a/src/Bundle/ChillCustomFieldsBundle/Controller/AdminController.php b/src/Bundle/ChillCustomFieldsBundle/Controller/AdminController.php index 1813dd7b3..eda55bb87 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Controller/AdminController.php +++ b/src/Bundle/ChillCustomFieldsBundle/Controller/AdminController.php @@ -12,6 +12,8 @@ declare(strict_types=1); namespace Chill\CustomFieldsBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; /** * Class AdminController @@ -19,8 +21,11 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; */ class AdminController extends AbstractController { - public function indexAction() + /** + * @Route("/{_locale}/admin/customfield/", name="customfield_section") + */ + public function indexAction(): Response { - return $this->render('ChillCustomFieldsBundle:Admin:layout.html.twig'); + return $this->render('@ChillCustomFields/Admin/layout.html.twig'); } } diff --git a/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php b/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php index 25e4d2359..0e0c23ab4 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php +++ b/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php @@ -17,14 +17,22 @@ use Chill\CustomFieldsBundle\Form\CustomFieldType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Contracts\Translation\TranslatorInterface; /** * Class CustomFieldController. */ class CustomFieldController extends AbstractController { + public function __construct(private readonly TranslatorInterface $translator) + { + } + /** * Creates a new CustomField entity. + * + * @Route("/{_locale}/admin/customfield/new", name="customfield_create") */ public function createAction(Request $request) { @@ -32,24 +40,21 @@ class CustomFieldController extends AbstractController $form = $this->createCreateForm($entity, $request->query->get('type', null)); $form->handleRequest($request); - if ($form->isValid()) { + if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($entity); $em->flush(); - $this->addFlash('success', $this->get('translator') + $this->addFlash('success', $this->translator ->trans('The custom field has been created')); - return $this->redirect($this->generateUrl( - 'customfieldsgroup_show', - ['id' => $entity->getCustomFieldsGroup()->getId()] - )); + return $this->redirectToRoute('customfieldsgroup_show', ['id' => $entity->getCustomFieldsGroup()->getId()]); } - $this->addFlash('error', $this->get('translator') + $this->addFlash('error', $this->translator ->trans('The custom field form contains errors')); - return $this->render('ChillCustomFieldsBundle:CustomField:new.html.twig', [ + return $this->render('@ChillCustomFields/CustomField/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), ]); @@ -58,9 +63,9 @@ class CustomFieldController extends AbstractController /** * Displays a form to edit an existing CustomField entity. * - * @param mixed $id + * @Route("/{_locale}/admin/customfield/edit", name="customfield_edit") */ - public function editAction($id) + public function editAction(mixed $id) { $em = $this->getDoctrine()->getManager(); @@ -72,7 +77,7 @@ class CustomFieldController extends AbstractController $editForm = $this->createEditForm($entity, $entity->getType()); - return $this->render('ChillCustomFieldsBundle:CustomField:edit.html.twig', [ + return $this->render('@ChillCustomFields/CustomField/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), ]); @@ -80,12 +85,14 @@ class CustomFieldController extends AbstractController /** * Displays a form to create a new CustomField entity. + * + * @Route("/{_locale}/admin/customfield/new", name="customfield_new") */ public function newAction(Request $request) { $entity = new CustomField(); - //add the custom field group if defined in URL + // add the custom field group if defined in URL $cfGroupId = $request->query->get('customFieldsGroup', null); if (null !== $cfGroupId) { @@ -94,28 +101,25 @@ class CustomFieldController extends AbstractController ->find($cfGroupId); if (!$cfGroup) { - throw $this->createNotFoundException('CustomFieldsGroup with id ' - . $cfGroupId . ' is not found !'); + throw $this->createNotFoundException('CustomFieldsGroup with id '.$cfGroupId.' is not found !'); } $entity->setCustomFieldsGroup($cfGroup); } $form = $this->createCreateForm($entity, $request->query->get('type')); - return $this->render('ChillCustomFieldsBundle:CustomField:new.html.twig', [ + return $this->render('@ChillCustomFields/CustomField/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), ]); } /** - * Finds and displays a CustomField entity. + * Edits an existing CustomField entity. * - * @deprecated is not used since there is no link to show action - * - * @param mixed $id + * @Route("/{_locale}/admin/customfield/update", name="customfield_update") */ - public function showAction($id) + public function updateAction(Request $request, mixed $id) { $em = $this->getDoctrine()->getManager(); @@ -125,41 +129,22 @@ class CustomFieldController extends AbstractController throw $this->createNotFoundException('Unable to find CustomField entity.'); } - return $this->render('ChillCustomFieldsBundle:CustomField:show.html.twig', [ - 'entity' => $entity, ]); - } - - /** - * Edits an existing CustomField entity. - * - * @param mixed $id - */ - public function updateAction(Request $request, $id) - { - $em = $this->getDoctrine()->getManager(); - - $entity = $em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomField::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find CustomField entity.'); - } - $editForm = $this->createEditForm($entity, $entity->getType()); $editForm->handleRequest($request); - if ($editForm->isValid()) { + if ($editForm->isSubmitted() && $editForm->isValid()) { $em->flush(); - $this->addFlash('success', $this->get('translator') + $this->addFlash('success', $this->translator ->trans('The custom field has been updated')); - return $this->redirect($this->generateUrl('customfield_edit', ['id' => $id])); + return $this->redirectToRoute('customfield_edit', ['id' => $id]); } - $this->addFlash('error', $this->get('translator') + $this->addFlash('error', $this->translator ->trans('The custom field form contains errors')); - return $this->render('ChillCustomFieldsBundle:CustomField:edit.html.twig', [ + return $this->render('@ChillCustomFields/CustomField/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), ]); @@ -168,13 +153,9 @@ class CustomFieldController extends AbstractController /** * Creates a form to create a CustomField entity. * - * @param CustomField $entity The entity - * @param string - * @param mixed $type - * * @return \Symfony\Component\Form\Form The form */ - private function createCreateForm(CustomField $entity, $type) + private function createCreateForm(CustomField $entity, mixed $type) { $form = $this->createForm(CustomFieldType::class, $entity, [ 'action' => $this->generateUrl( @@ -195,11 +176,10 @@ class CustomFieldController extends AbstractController * Creates a form to edit a CustomField entity. * * @param CustomField $entity The entity - * @param mixed $type * * @return \Symfony\Component\Form\Form The form */ - private function createEditForm(CustomField $entity, $type) + private function createEditForm(CustomField $entity, mixed $type) { $form = $this->createForm(CustomFieldType::class, $entity, [ 'action' => $this->generateUrl('customfield_update', ['id' => $entity->getId()]), diff --git a/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php b/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php index 4f180549a..a218e2cd2 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php +++ b/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php @@ -25,37 +25,25 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\Translation\TranslatorInterface; -use function in_array; /** * Class CustomFieldsGroupController. */ class CustomFieldsGroupController extends AbstractController { - /** - * @var CustomFieldProvider - */ - private $customfieldProvider; - - /** - * @var TranslatorInterface - */ - private $translator; - /** * CustomFieldsGroupController constructor. */ - public function __construct( - CustomFieldProvider $customFieldProvider, - TranslatorInterface $translator - ) { - $this->customfieldProvider = $customFieldProvider; - $this->translator = $translator; + public function __construct(private readonly CustomFieldProvider $customFieldProvider, private readonly TranslatorInterface $translator) + { } /** * Creates a new CustomFieldsGroup entity. + * + * @Route("/{_locale}/admin/customfieldsgroup/create", name="customfieldsgroup_create") */ public function createAction(Request $request) { @@ -63,7 +51,7 @@ class CustomFieldsGroupController extends AbstractController $form = $this->createCreateForm($entity); $form->handleRequest($request); - if ($form->isValid()) { + if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($entity); $em->flush(); @@ -71,13 +59,13 @@ class CustomFieldsGroupController extends AbstractController $this->addFlash('success', $this->translator ->trans('The custom fields group has been created')); - return $this->redirect($this->generateUrl('customfieldsgroup_show', ['id' => $entity->getId()])); + return $this->redirectToRoute('customfieldsgroup_show', ['id' => $entity->getId()]); } $this->addFlash('error', $this->translator ->trans('The custom fields group form contains errors')); - return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:new.html.twig', [ + return $this->render('@ChillCustomFields/CustomFieldsGroup/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), ]); @@ -86,13 +74,13 @@ class CustomFieldsGroupController extends AbstractController /** * Displays a form to edit an existing CustomFieldsGroup entity. * - * @param mixed $id + * @Route("/{_locale}/admin/customfieldsgroup/{id}/edit", name="customfieldsgroup_edit") */ - public function editAction($id) + public function editAction(mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class)->find($id); + $entity = $em->getRepository(CustomFieldsGroup::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find CustomFieldsGroup entity.'); @@ -100,7 +88,7 @@ class CustomFieldsGroupController extends AbstractController $editForm = $this->createEditForm($entity); - return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:edit.html.twig', [ + return $this->render('@ChillCustomFields/CustomFieldsGroup/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), ]); @@ -108,23 +96,25 @@ class CustomFieldsGroupController extends AbstractController /** * Lists all CustomFieldsGroup entities. + * + * @Route("/{_locale}/admin/customfieldsgroup/", name="customfieldsgroup") */ public function indexAction() { $em = $this->getDoctrine()->getManager(); - $cfGroups = $em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class)->findAll(); + $cfGroups = $em->getRepository(CustomFieldsGroup::class)->findAll(); $defaultGroups = $this->getDefaultGroupsId(); $makeDefaultFormViews = []; foreach ($cfGroups as $group) { - if (!in_array($group->getId(), $defaultGroups, true)) { + if (!\in_array($group->getId(), $defaultGroups, true)) { $makeDefaultFormViews[$group->getId()] = $this->createMakeDefaultForm($group)->createView(); } } - return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:index.html.twig', [ + return $this->render('@ChillCustomFields/CustomFieldsGroup/index.html.twig', [ 'entities' => $cfGroups, 'default_groups' => $defaultGroups, 'make_default_forms' => $makeDefaultFormViews, @@ -133,6 +123,8 @@ class CustomFieldsGroupController extends AbstractController /** * Set the CustomField Group with id $cFGroupId as default. + * + * @Route("/{_locale}/admin/customfieldsgroup/makedefault", name="customfieldsgroup_makedefault") */ public function makeDefaultAction(Request $request) { @@ -143,15 +135,13 @@ class CustomFieldsGroupController extends AbstractController $em = $this->getDoctrine()->getManager(); - $cFGroup = $em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class)->findOneById($cFGroupId); + $cFGroup = $em->getRepository(CustomFieldsGroup::class)->findOneById($cFGroupId); if (!$cFGroup) { - throw $this - ->createNotFoundException('customFieldsGroup not found with ' - . "id {$cFGroupId}"); + throw $this->createNotFoundException('customFieldsGroup not found with '."id {$cFGroupId}"); } - $cFDefaultGroup = $em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup::class) + $cFDefaultGroup = $em->getRepository(CustomFieldsDefaultGroup::class) ->findOneByEntity($cFGroup->getEntity()); if ($cFDefaultGroup) { @@ -173,18 +163,20 @@ class CustomFieldsGroupController extends AbstractController $this->addFlash('success', $this->translator ->trans('The default custom fields group has been changed')); - return $this->redirect($this->generateUrl('customfieldsgroup')); + return $this->redirectToRoute('customfieldsgroup'); } /** * Displays a form to create a new CustomFieldsGroup entity. + * + * @Route("/{_locale}/admin/customfieldsgroup/new", name="customfieldsgroup_new") */ public function newAction() { $entity = new CustomFieldsGroup(); $form = $this->createCreateForm($entity); - return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:new.html.twig', [ + return $this->render('@ChillCustomFields/CustomFieldsGroup/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), ]); @@ -204,7 +196,7 @@ class CustomFieldsGroupController extends AbstractController { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class)->find($id); + $entity = $em->getRepository(CustomFieldsGroup::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find CustomFieldsGroups entity.'); @@ -217,20 +209,20 @@ class CustomFieldsGroupController extends AbstractController $this->get('twig.loader') ->addPath( - __DIR__ . '/../Tests/Fixtures/App/app/Resources/views/', + __DIR__.'/../Tests/Fixtures/App/app/Resources/views/', $namespace = 'test' ); if ($form->isSubmitted()) { if ($form->get('submit_render')->isClicked()) { - return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:render_for_test.html.twig', [ + return $this->render('@ChillCustomFields/CustomFieldsGroup/render_for_test.html.twig', [ 'fields' => $form->getData(), 'customFieldsGroup' => $entity, ]); } - //dump($form->getData()); - //dump(json_enccode($form->getData())); + // dump($form->getData()); + // dump(json_enccode($form->getData())); } return $this @@ -242,13 +234,13 @@ class CustomFieldsGroupController extends AbstractController /** * Finds and displays a CustomFieldsGroup entity. * - * @param mixed $id + * @Route("/{_locale}/admin/customfieldsgroup/{id}/show", name="customfieldsgroup_show") */ - public function showAction($id) + public function showAction(mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class)->find($id); + $entity = $em->getRepository(CustomFieldsGroup::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find CustomFieldsGroup entity.'); @@ -256,7 +248,7 @@ class CustomFieldsGroupController extends AbstractController $options = $this->getOptionsAvailable($entity->getEntity()); - return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:show.html.twig', [ + return $this->render('@ChillCustomFields/CustomFieldsGroup/show.html.twig', [ 'entity' => $entity, 'create_field_form' => $this->createCreateFieldForm($entity)->createView(), 'options' => $options, @@ -266,13 +258,13 @@ class CustomFieldsGroupController extends AbstractController /** * Edits an existing CustomFieldsGroup entity. * - * @param mixed $id + * @Route("/{_locale}/admin/customfieldsgroup/{id}/update", name="customfieldsgroup_update") */ - public function updateAction(Request $request, $id) + public function updateAction(Request $request, mixed $id) { $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class)->find($id); + $entity = $em->getRepository(CustomFieldsGroup::class)->find($id); if (!$entity) { throw $this->createNotFoundException('Unable to find CustomFieldsGroup entity.'); @@ -281,19 +273,19 @@ class CustomFieldsGroupController extends AbstractController $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); - if ($editForm->isValid()) { + if ($editForm->isSubmitted() && $editForm->isValid()) { $em->flush(); $this->addFlash('success', $this->translator ->trans('The custom fields group has been updated')); - return $this->redirect($this->generateUrl('customfieldsgroup_edit', ['id' => $id])); + return $this->redirectToRoute('customfieldsgroup_edit', ['id' => $id]); } $this->addFlash('error', $this->translator ->trans('The custom fields group form contains errors')); - return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:edit.html.twig', [ + return $this->render('@ChillCustomFields/CustomFieldsGroup/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), ]); @@ -303,8 +295,7 @@ class CustomFieldsGroupController extends AbstractController { $fieldChoices = []; - foreach ($this->customfieldProvider->getAllFields() - as $key => $customType) { + foreach ($this->customFieldProvider->getAllFields() as $key => $customType) { $fieldChoices[$key] = $customType->getName(); } @@ -370,8 +361,6 @@ class CustomFieldsGroupController extends AbstractController /** * create a form to make the group default. * - * @param CustomFieldsGroup $group - * * @return \Symfony\Component\Form\Form */ private function createMakeDefaultForm(?CustomFieldsGroup $group = null) @@ -396,8 +385,8 @@ class CustomFieldsGroupController extends AbstractController $em = $this->getDoctrine()->getManager(); $customFieldsGroupIds = $em->createQuery('SELECT g.id FROM ' - . 'ChillCustomFieldsBundle:CustomFieldsDefaultGroup d ' - . 'JOIN d.customFieldsGroup g') + .CustomFieldsDefaultGroup::class.' d ' + .'JOIN d.customFieldsGroup g') ->getResult(Query::HYDRATE_SCALAR); $result = []; @@ -418,7 +407,7 @@ class CustomFieldsGroupController extends AbstractController private function getOptionsAvailable($entity) { $options = $this->getParameter('chill_custom_fields.' - . 'customizables_entities'); + .'customizables_entities'); foreach ($options as $key => $definition) { if ($definition['class'] === $entity) { diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php index 781b2b233..b57b45bc9 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php @@ -18,53 +18,32 @@ use Chill\CustomFieldsBundle\Form\Type\ChoicesType; use Chill\CustomFieldsBundle\Form\Type\ChoiceWithOtherType; use Chill\MainBundle\Form\Type\TranslatableStringFormType; use Chill\MainBundle\Templating\TranslatableStringHelper; -use LogicException; -use Symfony\Bridge\Twig\TwigEngine; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; - -use function array_key_exists; -use function count; -use function in_array; -use function is_array; -use function LogicException; +use Twig\Environment; class CustomFieldChoice extends AbstractCustomField { - public const ALLOW_OTHER = 'other'; + final public const ALLOW_OTHER = 'other'; - public const CHOICES = 'choices'; + final public const CHOICES = 'choices'; - public const EXPANDED = 'expanded'; + final public const EXPANDED = 'expanded'; - public const MULTIPLE = 'multiple'; + final public const MULTIPLE = 'multiple'; - public const OTHER_VALUE_LABEL = 'otherValueLabel'; - - private $defaultLocales; - - /** - * @var TwigEngine - */ - private $templating; - - /** - * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation - */ - private $translatableStringHelper; + final public const OTHER_VALUE_LABEL = 'otherValueLabel'; /** * CustomFieldChoice constructor. */ public function __construct( - TranslatorInterface $translator, - TwigEngine $templating, - TranslatableStringHelper $translatableStringHelper + private readonly Environment $templating, + /** + * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation + */ + private readonly TranslatableStringHelper $translatableStringHelper ) { - $this->defaultLocales = $translator->getFallbackLocales(); - $this->templating = $templating; - $this->translatableStringHelper = $translatableStringHelper; } public function allowOtherChoice(CustomField $cf) @@ -74,7 +53,7 @@ class CustomFieldChoice extends AbstractCustomField public function buildForm(FormBuilderInterface $builder, CustomField $customField) { - //prepare choices + // prepare choices $choices = []; $customFieldOptions = $customField->getOptions(); @@ -84,7 +63,7 @@ class CustomFieldChoice extends AbstractCustomField } } - //prepare $options + // prepare $options $options = [ 'multiple' => $customFieldOptions[self::MULTIPLE], 'choices' => array_combine(array_values($choices), array_keys($choices)), @@ -92,11 +71,11 @@ class CustomFieldChoice extends AbstractCustomField 'label' => $this->translatableStringHelper->localize($customField->getName()), ]; - //if allow_other = true + // if allow_other = true if (true === $customFieldOptions[self::ALLOW_OTHER]) { $otherValueLabel = null; - if (array_key_exists(self::OTHER_VALUE_LABEL, $customFieldOptions)) { + if (\array_key_exists(self::OTHER_VALUE_LABEL, $customFieldOptions)) { $otherValueLabel = $this->translatableStringHelper->localize( $customFieldOptions[self::OTHER_VALUE_LABEL] ); @@ -112,8 +91,8 @@ class CustomFieldChoice extends AbstractCustomField ) ->addModelTransformer(new CustomFieldDataTransformer($this, $customField)) ); - } else { //if allow_other = false - //we add the 'expanded' to options + } else { // if allow_other = false + // we add the 'expanded' to options $options['expanded'] = $customFieldOptions[self::EXPANDED]; $builder->add( @@ -198,7 +177,7 @@ class CustomFieldChoice extends AbstractCustomField if ($this->allowOtherChoice($cf)) { $labels = $cf->getOptions()[self::OTHER_VALUE_LABEL]; - if (!is_array($labels) || count($labels) === 0) { + if (!\is_array($labels) || 0 === \count($labels)) { $labels['back'] = 'other value'; } $choices['_other'] = $this->translatableStringHelper @@ -223,12 +202,12 @@ class CustomFieldChoice extends AbstractCustomField * * Used in list exports. * - * @param string $choiceSlug the slug of the choice we want to know if it was checked - * @param array|string $data the data of the field + * @param string $choiceSlug the slug of the choice we want to know if it was checked + * @param array|string $data the data of the field * * @return bool */ - public function isChecked(CustomField $cf, $choiceSlug, $data) + public function isChecked(CustomField $cf, $choiceSlug, array|string $data) { if (null === $data) { return false; @@ -236,10 +215,10 @@ class CustomFieldChoice extends AbstractCustomField if ($cf->getOptions()[self::MULTIPLE]) { if ($cf->getOptions()[self::ALLOW_OTHER]) { - return in_array($choiceSlug, $this->deserialize($data, $cf)['_choices'], true); + return \in_array($choiceSlug, $this->deserialize($data, $cf)['_choices'], true); } - return in_array($choiceSlug, $this->deserialize($data, $cf), true); + return \in_array($choiceSlug, $this->deserialize($data, $cf), true); } if ($cf->getOptions()[self::ALLOW_OTHER]) { @@ -256,9 +235,9 @@ class CustomFieldChoice extends AbstractCustomField } // if multiple choice OR multiple/single choice with other - if (is_array($value)) { + if (\is_array($value)) { // if allow other - if (array_key_exists('_choices', $value)) { + if (\array_key_exists('_choices', $value)) { if (null === $value['_choices']) { return true; } @@ -266,7 +245,7 @@ class CustomFieldChoice extends AbstractCustomField return empty($value['_choices']); } // we do not have 'allow other' - if (count($value) === 1) { + if (1 === \count($value)) { return empty($value[0]); } @@ -275,7 +254,7 @@ class CustomFieldChoice extends AbstractCustomField return empty($value); - throw LogicException('This case is not expected.'); + throw \LogicException('This case is not expected.'); } public function isMultiple(CustomField $cf) @@ -286,26 +265,23 @@ class CustomFieldChoice extends AbstractCustomField /** * @internal this function is able to receive data whichever is the value of "other", "multiple" * - * @param mixed $value - * @param mixed $documentType - * * @return string html representation */ public function render($value, CustomField $customField, $documentType = 'html') { - //extract the data. They are under a _choice key if they are stored with allow_other + // extract the data. They are under a _choice key if they are stored with allow_other $data = $value['_choices'] ?? $value; - $selected = (is_array($data)) ? $data : [$data]; + $selected = (\is_array($data)) ? $data : [$data]; $choices = $customField->getOptions()[self::CHOICES]; - if (in_array('_other', $selected, true)) { + if (\in_array('_other', $selected, true)) { $choices[] = ['name' => $value['_other'], 'slug' => '_other']; } - $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice.html.twig'; + $template = '@ChillCustomFields/CustomFieldsRendering/choice.html.twig'; if ('csv' === $documentType) { - $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice.csv.twig'; + $template = '@ChillCustomFields/CustomFieldsRendering/choice.csv.twig'; } return $this->templating @@ -316,7 +292,6 @@ class CustomFieldChoice extends AbstractCustomField 'selected' => $selected, 'multiple' => $customField->getOptions()[self::MULTIPLE], 'expanded' => $customField->getOptions()[self::EXPANDED], - 'locales' => $this->defaultLocales, ] ); } @@ -330,15 +305,14 @@ class CustomFieldChoice extends AbstractCustomField * deserialized the data from the database to a multiple * field. * - * @param mixed $serialized * @param bool $allowOther */ - private function deserializeToMultiple($serialized, $allowOther) + private function deserializeToMultiple(mixed $serialized, $allowOther) { $value = $this->guessValue($serialized); // set in an array : we want a multiple - $fixedValue = is_array($value) ? $value : [$value]; + $fixedValue = \is_array($value) ? $value : [$value]; if ($allowOther) { return $this->deserializeWithAllowOther($serialized, $fixedValue); @@ -352,9 +326,9 @@ class CustomFieldChoice extends AbstractCustomField $value = $this->guessValue($serialized); // set in a single value. We must have a single string - $fixedValue = is_array($value) ? + $fixedValue = \is_array($value) ? // check if the array has an element, if not replace by empty string - count($value) > 0 ? end($value) : '' + \count($value) > 0 ? end($value) : '' : $value; @@ -380,29 +354,24 @@ class CustomFieldChoice extends AbstractCustomField * * If the value had an 'allow_other' = true option, the returned value * **is not** the content of the _other field, but the `_other` string. - * - * @param array|string $value - * - * @throws LogicException if the case is not covered by this - * - * @return mixed */ - private function guessValue($value) + private function guessValue(array|string|null $value) { if (null === $value) { return null; } - if (!is_array($value)) { + if (!\is_array($value)) { return $value; } // we have a field with "allow other" - if (array_key_exists('_choices', $value)) { + if (\array_key_exists('_choices', $value)) { return $value['_choices']; } + // we have a field with "multiple" return $value; - throw LogicException('This case is not expected.'); + throw \LogicException('This case is not expected.'); } } diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php index 218bd5415..ecc97fbcc 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php @@ -15,14 +15,12 @@ use Chill\CustomFieldsBundle\Entity\CustomField; use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldDataTransformer; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Templating\TranslatableStringHelper; -use DateTime; -use Exception; -use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Twig\Environment; /** * Create a custom date number. @@ -33,31 +31,21 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class CustomFieldDate extends AbstractCustomField { - public const DATE_FORMAT = DateTime::RFC3339; + final public const DATE_FORMAT = \DateTime::RFC3339; - public const FORMAT = 'format'; + final public const FORMAT = 'format'; - public const MAX = 'max'; + final public const MAX = 'max'; /** * key for the minimal value of the field. */ - public const MIN = 'min'; + final public const MIN = 'min'; - /** - * @var TwigEngine - */ - private $templating; - - /** - * @var TranslatableStringHelper - */ - private $translatableStringHelper; - - public function __construct(TwigEngine $templating, TranslatableStringHelper $translatableStringHelper) - { - $this->templating = $templating; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly Environment $templating, + private readonly TranslatableStringHelper $translatableStringHelper + ) { } public function buildForm(FormBuilderInterface $builder, CustomField $customField) @@ -81,8 +69,8 @@ class CustomFieldDate extends AbstractCustomField { $validatorFunction = static function ($value, ExecutionContextInterface $context) { try { - $date = new DateTime($value); - } catch (Exception $e) { + $date = new \DateTime((string) $value); + } catch (\Exception) { $context->buildViolation('The expression "%expression%" is invalid', [ '%expression%' => $value, ]) @@ -117,7 +105,7 @@ class CustomFieldDate extends AbstractCustomField return null; } - return DateTime::createFromFormat(self::DATE_FORMAT, $serialized); + return \DateTime::createFromFormat(self::DATE_FORMAT, $serialized); } public function getName() @@ -138,8 +126,8 @@ class CustomFieldDate extends AbstractCustomField return $date->format('Y-m-d'); default: - $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:date.' - . $documentType . '.twig'; + $template = '@ChillCustomFields/CustomFieldsRendering/date.' + .$documentType.'.twig'; return $this->templating ->render($template, [ @@ -175,7 +163,7 @@ class CustomFieldDate extends AbstractCustomField // add required $fieldOptions['required'] = false; - //add label + // add label $fieldOptions['label'] = $this->translatableStringHelper->localize($customField->getName()); // add constraints if required @@ -186,8 +174,8 @@ class CustomFieldDate extends AbstractCustomField return; } - $value = DateTime::createFromFormat(self::DATE_FORMAT, $timestamp); - $after = new DateTime($options[self::MIN]); + $value = \DateTime::createFromFormat(self::DATE_FORMAT, $timestamp); + $after = new \DateTime($options[self::MIN]); if ($value < $after) { $context @@ -207,8 +195,8 @@ class CustomFieldDate extends AbstractCustomField return; } - $value = DateTime::createFromFormat(self::DATE_FORMAT, $timestamp); - $before = new DateTime($options[self::MAX]); + $value = \DateTime::createFromFormat(self::DATE_FORMAT, $timestamp); + $before = new \DateTime($options[self::MAX]); if ($value > $before) { $context diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php index 43902a7e3..eb2d3b011 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php @@ -21,7 +21,7 @@ interface CustomFieldInterface * user. * * @param \Chill\CustomFieldsBundle\CustomField\FormBuilderInterface $builder - * @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField + * @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField * * @return \Symfony\Component\Form\FormTypeInterface the form type */ @@ -42,7 +42,6 @@ interface CustomFieldInterface * value which may be used in the process. * * @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField - * @param mixed $serialized */ public function deserialize($serialized, CustomField $customField); @@ -50,26 +49,21 @@ interface CustomFieldInterface /** * Return if the value can be considered as empty. - * - * @param mixed $value the value passed throug the deserialize function */ - public function isEmptyValue($value, CustomField $customField); + public function isEmptyValue(mixed $value, CustomField $customField); /** * Return a repsentation of the value of the CustomField. * - * @param mixed $value the raw value, **not deserialized** (= as stored in the db) * @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField - * @param mixed $documentType * * @return string an html representation of the value */ - public function render($value, CustomField $customField, $documentType = 'html'); + public function render(mixed $value, CustomField $customField, $documentType = 'html'); /** * Transform the value into a format that can be stored in DB. * - * @param mixed $value * @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField */ public function serialize($value, CustomField $customField); diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php index 75dc7792f..b524eca5d 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php @@ -17,33 +17,18 @@ use Chill\CustomFieldsBundle\EntityRepository\CustomFieldLongChoice\OptionReposi use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldDataTransformer; use Chill\MainBundle\Form\Type\Select2ChoiceType; use Chill\MainBundle\Templating\TranslatableStringHelper; -use LogicException; -use Symfony\Bridge\Twig\TwigEngine; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; -use function get_class; -use function gettype; -use function is_object; - class CustomFieldLongChoice extends AbstractCustomField { - public const KEY = 'key'; - - private OptionRepository $optionRepository; - - private TwigEngine $templating; - - private TranslatableStringHelper $translatableStringHelper; + final public const KEY = 'key'; public function __construct( - OptionRepository $optionRepository, - TranslatableStringHelper $translatableStringHelper, - TwigEngine $twigEngine + private readonly OptionRepository $optionRepository, + private readonly TranslatableStringHelper $translatableStringHelper, + private readonly \Twig\Environment $templating, ) { - $this->optionRepository = $optionRepository; - $this->translatableStringHelper = $translatableStringHelper; - $this->templating = $twigEngine; } public function buildForm(FormBuilderInterface $builder, CustomField $customField) @@ -54,7 +39,7 @@ class CustomFieldLongChoice extends AbstractCustomField false, true ); - //create a local copy of translatable string helper + // create a local copy of translatable string helper $translatableStringHelper = $this->translatableStringHelper; $builder->add($customField->getSlug(), Select2ChoiceType::class, [ 'choices' => $entries, @@ -81,7 +66,7 @@ class CustomFieldLongChoice extends AbstractCustomField public function buildOptionsForm(FormBuilderInterface $builder) { - //create a selector between different keys + // create a selector between different keys $keys = $this->optionRepository->getKeys(); $choices = []; @@ -112,8 +97,8 @@ class CustomFieldLongChoice extends AbstractCustomField public function render($value, CustomField $customField, $documentType = 'html') { $option = $this->deserialize($value, $customField); - $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice_long.' - . $documentType . '.twig'; + $template = '@ChillCustomFields/CustomFieldsRendering/choice_long.' + .$documentType.'.twig'; return $this->templating ->render($template, [ @@ -128,9 +113,7 @@ class CustomFieldLongChoice extends AbstractCustomField } if (!$value instanceof Option) { - throw new LogicException('the value should be an instance of ' - . 'Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option, ' - . is_object($value) ? get_class($value) : gettype($value) . ' given'); + throw new \LogicException('the value should be an instance of Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option, '.\is_object($value) ? $value::class : \gettype($value).' given'); } // we place the id in array, to allow in the future multiple select diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php index 82b35f9f5..27dfce42c 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php @@ -13,13 +13,13 @@ namespace Chill\CustomFieldsBundle\CustomFields; use Chill\CustomFieldsBundle\Entity\CustomField; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; use Symfony\Component\Validator\Constraints\LessThanOrEqual; +use Twig\Environment; /** * Create a custom field number. @@ -28,41 +28,31 @@ use Symfony\Component\Validator\Constraints\LessThanOrEqual; */ class CustomFieldNumber extends AbstractCustomField { - public const MAX = 'max'; + final public const MAX = 'max'; /** * key for the minimal value of the field. */ - public const MIN = 'min'; + final public const MIN = 'min'; - public const POST_TEXT = 'post_text'; + final public const POST_TEXT = 'post_text'; - public const SCALE = 'scale'; + final public const SCALE = 'scale'; - /** - * @var TwigEngine - */ - private $templating; - - /** - * @var TranslatableStringHelper - */ - private $translatableStringHelper; - - public function __construct(TwigEngine $templating, TranslatableStringHelper $translatableStringHelper) - { - $this->templating = $templating; - $this->translatableStringHelper = $translatableStringHelper; + public function __construct( + private readonly Environment $templating, + private readonly TranslatableStringHelper $translatableStringHelper + ) { } public function buildForm(FormBuilderInterface $builder, CustomField $customField) { $options = $customField->getOptions(); - //select the type depending to the SCALE + // select the type depending to the SCALE $type = (0 === $options[self::SCALE] || null === $options[self::SCALE]) ? IntegerType::class : NumberType::class; - //'integer' : 'number'; + // 'integer' : 'number'; $fieldOptions = $this->prepareFieldOptions($customField, $type); @@ -106,8 +96,8 @@ class CustomFieldNumber extends AbstractCustomField public function render($value, CustomField $customField, $documentType = 'html') { - $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:number.' - . $documentType . '.twig'; + $template = '@ChillCustomFields/CustomFieldsRendering/number.' + .$documentType.'.twig'; $options = $customField->getOptions(); return $this->templating @@ -142,7 +132,7 @@ class CustomFieldNumber extends AbstractCustomField // add required $fieldOptions['required'] = false; - //add label + // add label $fieldOptions['label'] = $this->translatableStringHelper->localize($customField->getName()); // add constraints if required diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php index 0a6efc2bc..6e9a3df0e 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php @@ -13,42 +13,23 @@ namespace Chill\CustomFieldsBundle\CustomFields; use Chill\CustomFieldsBundle\Entity\CustomField; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\HttpFoundation\RequestStack; - -use function array_key_exists; +use Twig\Environment; class CustomFieldText extends AbstractCustomField { - public const MAX_LENGTH = 'maxLength'; + final public const MAX_LENGTH = 'maxLength'; - public const MULTIPLE_CF_INLINE = 'multipleCFInline'; - - private $requestStack; - - /** - * @var TwigEngine - */ - private $templating; - - /** - * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation - */ - private $translatableStringHelper; + final public const MULTIPLE_CF_INLINE = 'multipleCFInline'; public function __construct( - RequestStack $requestStack, - TwigEngine $templating, - TranslatableStringHelper $translatableStringHelper + private readonly Environment $templating, + private readonly TranslatableStringHelper $translatableStringHelper ) { - $this->requestStack = $requestStack; - $this->templating = $templating; - $this->translatableStringHelper = $translatableStringHelper; } /** @@ -67,7 +48,7 @@ class CustomFieldText extends AbstractCustomField $attrArray = []; if ( - array_key_exists(self::MULTIPLE_CF_INLINE, $options) + \array_key_exists(self::MULTIPLE_CF_INLINE, $options) && $options[self::MULTIPLE_CF_INLINE] ) { $attrArray['class'] = 'multiple-cf-inline'; @@ -106,10 +87,10 @@ class CustomFieldText extends AbstractCustomField public function render($value, CustomField $customField, $documentType = 'html') { - $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:text.html.twig'; + $template = '@ChillCustomFields/CustomFieldsRendering/text.html.twig'; if ('csv' === $documentType) { - $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:text.csv.twig'; + $template = '@ChillCustomFields/CustomFieldsRendering/text.csv.twig'; } return $this->templating diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php index b22b84029..07a0a0121 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php @@ -14,39 +14,25 @@ namespace Chill\CustomFieldsBundle\CustomFields; use Chill\CustomFieldsBundle\Entity\CustomField; use Chill\CustomFieldsBundle\Form\Type\CustomFieldsTitleType; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\HttpFoundation\RequestStack; +use Twig\Environment; class CustomFieldTitle extends AbstractCustomField { - public const TYPE = 'type'; + final public const TYPE = 'type'; - public const TYPE_SUBTITLE = 'subtitle'; + final public const TYPE_SUBTITLE = 'subtitle'; - public const TYPE_TITLE = 'title'; - - private $requestStack; - - /** - * @var TwigEngine - */ - private $templating; - - /** - * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation - */ - private $translatableStringHelper; + final public const TYPE_TITLE = 'title'; public function __construct( - RequestStack $requestStack, - TwigEngine $templating, - TranslatableStringHelper $translatableStringHelper + private readonly Environment $templating, + /** + * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation + */ + private readonly TranslatableStringHelper $translatableStringHelper ) { - $this->requestStack = $requestStack; - $this->templating = $templating; - $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder, CustomField $customField) @@ -95,7 +81,7 @@ class CustomFieldTitle extends AbstractCustomField { return $this->templating ->render( - 'ChillCustomFieldsBundle:CustomFieldsRendering:title.html.twig', + '@ChillCustomFields/CustomFieldsRendering/title.html.twig', [ 'title' => $customField->getName(), 'type' => $customField->getOptions()[self::TYPE], diff --git a/src/Bundle/ChillCustomFieldsBundle/DataFixtures/ORM/LoadOption.php b/src/Bundle/ChillCustomFieldsBundle/DataFixtures/ORM/LoadOption.php index 5b2598ab7..f95991b7c 100644 --- a/src/Bundle/ChillCustomFieldsBundle/DataFixtures/ORM/LoadOption.php +++ b/src/Bundle/ChillCustomFieldsBundle/DataFixtures/ORM/LoadOption.php @@ -37,7 +37,7 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface */ public $fakerNl; - private $counter = 0; + private int $counter = 0; public function __construct() { @@ -73,7 +73,7 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface ->setText($text) ->setParent($parent) ->setActive(true) - ->setInternalKey($parent->getKey() . '-' . $this->counter); + ->setInternalKey($parent->getKey().'-'.$this->counter); } private function loadingCompanies(ObjectManager $manager) @@ -103,7 +103,7 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface ->setKey('company'); $manager->persist($parent); - //Load children + // Load children $expected_nb_children = random_int(10, 50); for ($i = 0; $i < $expected_nb_children; ++$i) { @@ -143,7 +143,7 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface ->setKey('word'); $manager->persist($parent); - //Load children + // Load children $expected_nb_children = random_int(10, 50); for ($i = 0; $i < $expected_nb_children; ++$i) { diff --git a/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/ChillCustomFieldsExtension.php b/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/ChillCustomFieldsExtension.php index a90f770a4..724fd01ca 100644 --- a/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/ChillCustomFieldsExtension.php +++ b/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/ChillCustomFieldsExtension.php @@ -29,15 +29,15 @@ class ChillCustomFieldsExtension extends Extension implements PrependExtensionIn $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services.yaml'); $loader->load('services/fixtures.yaml'); $loader->load('services/controller.yaml'); $loader->load('services/command.yaml'); $loader->load('services/menu.yaml'); - //add at least a blank array at 'customizable_entities' options - //$customizable_entities = (isset($config['customizables_entities']) + // add at least a blank array at 'customizable_entities' options + // $customizable_entities = (isset($config['customizables_entities']) // && $config['customizables_entities'] !== FALSE) // ? $config['customizables_entities'] : array(); @@ -59,12 +59,12 @@ class ChillCustomFieldsExtension extends Extension implements PrependExtensionIn // add form layout to twig resources $twigConfig = [ 'form_themes' => [ - 'ChillCustomFieldsBundle:Form:fields.html.twig', + '@ChillCustomFields/Form/fields.html.twig', ], ]; $container->prependExtensionConfig('twig', $twigConfig); - //add routes for custom bundle + // add routes for custom bundle $container->prependExtensionConfig('chill_main', [ 'routing' => [ 'resources' => [ diff --git a/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/CustomFieldCompilerPass.php b/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/CustomFieldCompilerPass.php index 0cf1a67eb..6e68a248a 100644 --- a/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/CustomFieldCompilerPass.php +++ b/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/CustomFieldCompilerPass.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\CustomFieldsBundle\DependencyInjection; -use LogicException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -21,8 +20,7 @@ class CustomFieldCompilerPass implements CompilerPassInterface public function process(ContainerBuilder $container) { if (!$container->hasDefinition('chill.custom_field.provider')) { - throw new LogicException('service chill.custom_field.provider ' - . 'is not defined.'); + throw new \LogicException('service chill.custom_field.provider is not defined.'); } $definition = $container->getDefinition( diff --git a/src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php b/src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php index d9f43ed3b..6d1373fb0 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php +++ b/src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php @@ -17,39 +17,37 @@ use Doctrine\ORM\Mapping as ORM; * CustomField. * * @ORM\Entity + * * @ORM\Table(name="customfield") + * * @ORM\HasLifecycleCallbacks */ class CustomField { - public const ONE_TO_MANY = 2; + final public const ONE_TO_MANY = 2; - public const ONE_TO_ONE = 1; + final public const ONE_TO_ONE = 1; /** - * @var bool - * * @ORM\Column(type="boolean") */ - private $active = true; + private bool $active = true; /** - * @var CustomFieldsGroup - * * @ORM\ManyToOne( * targetEntity="Chill\CustomFieldsBundle\Entity\CustomFieldsGroup", * inversedBy="customFields") */ - private $customFieldGroup; + private ?CustomFieldsGroup $customFieldGroup = null; /** - * @var int - * * @ORM\Id + * * @ORM\Column(name="id", type="integer") + * * @ORM\GeneratedValue(strategy="AUTO") */ - private $id; + private ?int $id = null; /** * @var array @@ -59,39 +57,29 @@ class CustomField private $name; /** - * @var array - * * @ORM\Column(type="json") */ - private $options = []; + private array $options = []; /** - * @var float - * * @ORM\Column(type="float") */ - private $ordering; + private ?float $ordering = null; /** - * @var bool - * * @ORM\Column(type="boolean") */ - private $required = false; + private false $required = false; /** - * @var string - * * @ORM\Column(type="string", length=255) */ - private $slug; + private ?string $slug = null; /** - * @var string - * * @ORM\Column(type="string", length=255) */ - private $type; + private ?string $type = null; /** * Get customFieldGroup. @@ -222,8 +210,6 @@ class CustomField /** * Set customFieldGroup. * - * @param CustomFieldsGroup $customFieldGroup - * * @return CustomField */ public function setCustomFieldsGroup(?CustomFieldsGroup $customFieldGroup = null) @@ -281,8 +267,6 @@ class CustomField } /** - * @param $slug - * * @return $this */ public function setSlug($slug) diff --git a/src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldLongChoice/Option.php b/src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldLongChoice/Option.php index 08233886d..2d9889066 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldLongChoice/Option.php +++ b/src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldLongChoice/Option.php @@ -11,67 +11,71 @@ declare(strict_types=1); namespace Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity( * repositoryClass="Chill\CustomFieldsBundle\EntityRepository\CustomFieldLongChoice\OptionRepository") + * * @ORM\Table(name="custom_field_long_choice_options") */ class Option { /** - * @var bool * @ORM\Column(type="boolean") */ - private $active = true; + private bool $active = true; /** - * @var Collection + * @var Collection