diff --git a/.changes/header.tpl.md b/.changes/header.tpl.md new file mode 100644 index 000000000..df8faa7b2 --- /dev/null +++ b/.changes/header.tpl.md @@ -0,0 +1,6 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), +and is generated by [Changie](https://github.com/miniscruff/changie). diff --git a/src/Bundle/ChillMainBundle/config/routes/notification.yaml b/.changes/unreleased/.gitkeep similarity index 100% rename from src/Bundle/ChillMainBundle/config/routes/notification.yaml rename to .changes/unreleased/.gitkeep diff --git a/.changes/v2.0.0.md b/.changes/v2.0.0.md new file mode 100644 index 000000000..8d70ecba6 --- /dev/null +++ b/.changes/v2.0.0.md @@ -0,0 +1,677 @@ +## 2.0.0 + +* this is a release to relaunch our proceess of release with semantic versioning + +## Test releases + +### 2.0.0-beta3 + +* [person][export] Fixed: rename the alias for `accompanying_period` to `acp` in filter associated with person +* [activity][export] Feature: improve label for aliases in "Filter by activity type" +* [activity][export] DX/Feature: use of an `ActivityTypeRepositoryInterface` instead of the old-style EntityRepository +* [person][export] Fixed: some inconsistency with date filter on accompanying courses +* [person][export] Fixed: use left join for related entities in accompanying course aggregators +* [workflow] Feature: allow user to copy and send manually the access link for the workflow +* [workflow] Feature: show the email addresses that received an access link for the workflow +### 2.0.0-beta2 + +* [workflow]: Fixed: the notification is sent when the user is added to the first step. +* [budget] Feature: allow to desactivate some charges and resources, adding an `active` key in the configuration +* [person] Feature: on Evaluation, allow to configure an URL from the admin + +### 2022-06 + +* [workflow]: added pagination to workflow list page +* [homepage_widget]: null error on tasks widget fixed +* [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty +* [search]: Order of birthdate fields changed in advanced search to avoid confusion. +* [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675) +* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625) +* [household]: Reposition and cut button for enfant hors menage have been deleted (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/620) +* [admin]: Add crud for composition type in admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/611) +* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625) + +## Test releases + +### 2022-05-30 + +* fix creating a new AccompanyingPeriodWorkEvaluationDocument when replacing the document (the workflow was lost) + +### 2022-05-27 + +* [storedobject] add title field on StoredObject entity + use it in activity documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/604) +* [main] add a "read more..." on comment embeddable when overflown (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/604) +* [person] add closing motive to closed acc course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/603) +* [person] household filiation: fetch person info when unfolding person (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/586) +* [admin] repair edit of social action in the admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/601) +* [admin]: add select2 to Goal form type entity fields (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/702) +* [main] allow hide permissions group list menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577) +* [main] allow hide change user password menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577) +* [main] filter user jobs by active jobs (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577) +* [main] add civility to User (entity, migration and form type) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577) +* [admin] refactorisation of the admin section: reorganisation of the menu, translations, form types, new entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/592) +* [admin] add admin section for languages and countries (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/596) +* [activity] activity admin: translations + remove label field for comment on admin activity type (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/587) +* [main] admin user_job: improvements (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/588) +* [address] can add extra address info even if noAddress (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/576) + + +### 2022-05-06 + +* [person] add civility when creating a person (with the on-the-fly component or in the php form) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557) +* [person] add address when creating a person (with the on-the-fly component or in the php form) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557) +* [person] add household creation API point (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557) + +### 2021-04-29 + +* [person] prevent circular references in PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/527) +* [person] add maritalStatusComment to PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/582) +* Load relationships without gender in french fixtures +* Add command to remove old draft accompanying periods +* [parcours]: If users assings him/herself as referrer and job is not null. Update parcours job (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/578) + +### 2021-04-28 + +* [address] fix bug when editing address: update location and addressreferenceId + better update of the map in edition (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/593) +* [main] avoid address reference search on undefined post code (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/561) +* [person] prevent duplicate relationship in filiation/household graph (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/560) +* [Documents] Validate storedObject and allow for null data (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/565) +* [parcours]: Comments can be unpinned + edit/delete for all users that are allowed to edit parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/566) + +### 2021-04-26 + +* [Datepickers] datepickers fixed when using keyboard to enter date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/545) +* [social_action] Display 'agents traitants' in parcours resumé and social action list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/568) +* [Person_search] Closed parcours shown within an accordeon that can be opened/closed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/574) + +### 2021-04-24 + +* [notification email on course designation] allow raw string in email content generation +* [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older +* [Documents] Change wording 'créer' to 'enregistrer' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/634) +* [Parcours]: The number of 'mes parcours' displayed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/572) +* [Hompage_widget]: Renaming of tabs and removal of social actions tab (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/570) +* [activity]: Ignore thirdparties when creating a social action via an activity (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/573) +* [parcours]: change wording of warning message and button when user is not associated to a household yet (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/590#note_918370943) +* [Accompanying period work evaluations] list documents associated to a work by creation date, and then by id, from the most recent to older +* [Course comment] add validationConstraint NotNull and NotBlank on comment content, to avoid sql error +* [Notifications] delay the sending of notificaiton to kernel.terminate +* [Notifications / Period user change] fix the sending of notification when user changes +* [Activity form] invert 'incoming' and 'receiving' in Activity form +* [Activity form] keep the same order for 'attendee' field in new and edit form +* [list with period] use "sameas" test operator to introduce requestor in list +* [notification email on course designation] allow raw string in email content generation +* [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older +* [evaluation_document] changing date to datetime in order to display the time at which document was created (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/569) + + +### 2021-04-13 + +* [person] household address: add a form for editing the validFrom date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/541) +* [person] householdmemberseditor: fix composition type bug in select form (vuejs) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/543) +* [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); +* [docgen] add a new context with a list of activities in course +* [docgen] add a comment in budget lines +* [notifications] allow to send a notification to an email address. The address receive an access link +* [adresses] add constraints in database to avoid errors later: postcode not null, and validfrom <= validto +* [accompanying work editor] add a label on document title input + +### 2021-04-07 + +* notification list: move action buttons outside of the toggle +* fix detecting of non-read notification +* filter users which are disabled in search user api +* order query for location and add pagination in list +* allow every person which has part for a workflow to see the workflow page +* able to see the workflow if the evaluation document has been deleted +* hardcode the list of supported mime types for edition with collabora +* list of accompanying course: allow to see the pinned comment in list_item + +### 2021-04-06 + +* [main] notification toggle read: correct js syntax for compilation in production (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/548) +* [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535) +* [activity] espace entre les boutons pour supprimer les documents + + +### continuous release in February and March + +* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType +* [person] Accompanying course evaluation documents: disable the WOPI edit link if mimetype not supported and if no keyInfos +(https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/585) +* [activity] display error messages above the form in creating a new location (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/481) +* [activity] show required field in activity edit/new by an asterix in the vuejs fields (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/494) +* [ACL] fix allow to see the course, event if the scope'course does not contains the scope's user +* [search] enforce limit of results for fetching rsults by search api https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/576 +* [activity] Fix delete button for document (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/554) +* [activity] Add return path the document generation (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/553) +* [person] add person ressource to person docgen normaliser (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/517) +* [person] AccompanyingCourseWorkEdit: fix deleting evaluation documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/546) +* [person] AccompanyingCourseWorkEdit: download existing documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/512) +* [person] AccompanyingCourseWorkEdit: replace document by a new one (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/511) +* [person] AccompanyingPeriodWork: add referrers to work, add doctrine event listener to add logged user to referrers collection and display a referrers list in work list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/502) +* [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495) +* [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493) +* [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477) +* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472) +* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472) +* [person] Add url in accompanying period work evaluations entity and form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/476) +* [person] Add document generation in admin and in person/{id}/document (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/464) +* [activity] do not override location if already exist (when validating new activity) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/470) +* [parcours] Toggle emergency/intensity only by referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/442) +* [docstore] Add an API entrypoint for StoredObject (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466) +* [person] Add the possibility of uploading existing documents to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466) +* [person] Add title to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466) +* [person] Order social issues by the field "ordering" (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/388) +* [Person/Household list] when listing other simultaneous members of an household, exclude the members on person, not on members (avoid to show two membersship with the same person) +* [draft periods] add a delete button (if acl granted) on each draft period listed on draft period page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/463) +* [Person] Display suffixText in RenderPerson, PersonText.vue, RenderPersonBox.vue (was made for displaying "enfant confie") (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/441) +* [budget]: budget enabled for persons and households (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/469) +* [person] residential address: show residential address or info in PersonRenderBox, refactor Residential Address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/439) +* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345) +* [documents] Improve flex-table item-col placement when long buttons and long metadata +* [thirdparty] Fix display of multiple contact badges so they wrap onto next line (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/482) +* [confidential] Fix position of toggle button so it does not cover text nor fall outside of box (no issue) +* [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474) +* [template] do not list inactive templates (for doc generator) +* [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480) +* [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478) +* [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490) +* [activity] Only youngest descendant is kept for social issues and actions (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/471) +* [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484) +* [address] person badge in address history changed to open OnTheFly with all person info (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/489) +* [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499) +* [thirdparty] Add parameter condition to display centers or not (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/500) +* [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496) +* [person_resource] separate create page created to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/504) +* [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506) +* [thirdparty] For contacts show current civility/profession in edit form + fix saving of edited information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/491) +* [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505) +* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486) +* [thirdparty] add firstname field to thirdparty 'child' or 'contact' types (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/508) +* [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505) +* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486) +* [parcours] List of parcours for a specific user so they can be reassigned in case of absence (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/509) +* [thirdparty] Thirdparty view page, english text translated (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/534) +* [social_action] Translation changed in evaluation section (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/512) +* [filiation] Possible to add person (or create onthefly) to add to filiation graph + add relation (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/519) +* [household] Within parcours listing page of household add create button (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/560) +* [person_resource] bugfix when adding thirdparty or freetext resource + prevent personOwner themselves to be added. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/526) +* [aside_activity] style correction + sticky-form create button (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/529) +* [budget] order within the menu adjusted (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/592) +* [onthefly] fix create person. Bug was noticed in filiation (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/591) +* [parcours] Create document buttons made sticky (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/532) +* [person] Trailing guillemet removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/530) +* [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537) +* [onthefly] trim trailing whitespace in email of person and thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/542) + +* [action] Only youngest descendant is kept for social issues and actions (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/471) +## Test releases + +### test release 2022-02-21 + +* [notifications] Word 'un' changed to number '1' for notifications in user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/483) +* [documents] 'gabarit' changed to 'modèle' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/405) +* [person_resources] Menu name and order changed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/460) +* workflow: fix sending notifications +* [thirdparty] Extend the thirdparty search to thirdparty children (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/448) +* [person]: AddPersons: allow creation of person or thirdparty only (no users) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422) +* [person]: AddPersons: allow creation of person or thirdparty depending on allowed types (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422) +* [person]: AddPersons: add suggestion of name when creating new person or thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422) +* [main] Address: fix small bug: when modifying an address without street (isNoAddress), also check errors if street is an empty string as back-end change null value to empty string for street (and streetNumber) +* [main] Address: stronger client-side validation of addresses (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/449) +* [person] accompanying course: filter suggested entities by open participations (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/415) +[activity] can click through the cross icon for removing person in concerned group (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/476) +[activity] correct associated persons by considering only open participations (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/476) +* [person_resources]: Renderboxes used to display person/thirdparty info (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/465) +* [Household]: Add end date in HouseholdMember form for 'enfant hors menage' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/434) +* [homepage_widget]: If no sender then display as 'notification automatique' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/435) +* [parcours]: Order social activities and only display most recent three in parcours resumé (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/481) +* [3party]: 3party: redirect to parent when contact (child) is opened in view page +* [parcours / addresses]: launch an event when a person change address (either through changing household or because the household is associated to a new address). If the person is localising a course, the course location go back to a temporarily address. +* [thirdparty]: address/phonenumber/email/fonction displayed in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/401) +* [thirdparty_contact]: in search results the 'qualité' is displayed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/465) +* [bug]: fix confidential toggle of address in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/460) + + + +### test release 2022-02-14 + +* AddPersons: remove ul-li html tags from AddPersons (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/419) +* [doc-generator] do not set required fields for mainPerson, person1, person2 (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement#456) +* [doc-generation] add age and obele in the mainPerson, person1 and person2 list + add obele in person renderString if addAge (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/370) +* [person] accompanying course work: fix on-the-fly update of thirdParty +* fix normalisation of accompanying course requestor api (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/378) +* [person] add a returnPath when clicking on some Person or ThirdParty badge (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/427) +* [person] accompanying course work: fix on-the-fly update of thirdParty +* [on-the-fly] close modal only after validation +* [person] correct thirdparty PATCH url + add email and altnames in AddPerson and serializer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/433) +* change order for accompanying course work list +* [parcours]: Mes parcours brouillon added to user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/440) +* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414) +* [person]: style fix in parcours listing per person. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/432) +* [parcours]: Only the referrer can toggle the intensity of the parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/442) +* [household]: display address of current household (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/415) +* ajoute un ordre dans les localisation (api) +* [pick entity]: fix translations in modal (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/419) +* [homepage_widget]: fix translation on emergency badge (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/440) +* [person]: create person and household added to button dropdown (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/454) +* display full address in address.text in normalization. Adapt AddressRenderBox +* [address]: Correction residential address 'depuis le' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/459) +* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414) +* [Thirdparty_contact]: address blurred if confidential in view page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/450) +* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345) + + +### test release 2021-02-01 + +* renommer "dossier numéro" en "parcours numéro" dans les résultats de recherche +* renomme date de début en date d'ouverture dans le formulaire parcours +* [homepage widget] improve content tables, improve counter pluralization with style on number +* [notification lists] add comments counter information +* [workflows] fix popover header with previous transition +* [parcours]: validation + message for closing parcours adjusted. +* [household]: household composition double edit button replaced by a delete action (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/426) +[fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413 +[homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc. +* [person]: Comment on marital status is possible even if marital status is not defined (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/421) +* [parcours]: In the list of person results the requestor is not displayed if defined as anonymous (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/424) +* [bugfix]: modal closes and newly created person/thirdparty is selected when multiple persons/thirdparties are created through the modal (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/429) +* [person_resource]: Onthefly button added to view person/thirdparty and badge differentiation for a contact-thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/428) +* [workflow][notification] improve how notifications and workflows are 'attached' to entities: contextual list, counter, buttons and vue modal +* [AddAddress] disable multiselect search, and rely only on most pertinent Cities and Street computed backend +* [fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413 +* [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc. +* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345) +* [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc. + + +### test release 2021-01-31 + +* [person] accompanying course: optimisation: do not fetch some resources for the banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/409) +* [person] accompanying course: close modal when edit participation (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420) +* [person] accompanying course: treat validation error when editing on-the-fly entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420) +* [activity] show activity attendee (présence) in the activity list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/412) +* [activity] admin: change validation rule for social action visible field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/413) +* [parcours]: component added to change the opening date of a parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/411) +* [search]: listing of parcours display changed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/410) +* [user]: page with accompanying periods to which is user is referent (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/408) +* [person] age added to renderstring + renderbox/ vue component created to display person text (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/389) +* [household member editor] allow to push to existing household + + +### test release 2021-01-28 + +* [person] improve filiations vis graph: disable physics, use chill colors for persons-households-course, increase label of relations, remove labels on household arrows and other improvements (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/286, https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/362) +* [activity] Order activity by date and by id (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/364) +* [main] increase length of 4 Address fields (change to TEXT, no size limits) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/277) +* [main] Add confidential option for address, in edit and view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/165) +* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377) +* [person] Add residential address entity, form and list for each person +* [aside_activity]: dynamicUserPickerType used (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/399) +* dispatching list + + +### test release 2021-01-26 + +* [parcours] comments truncated if too long + link added (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/406) +* [person]: possibility to add person resources (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/382) +* [person ressources]: module added + + +### test release 2022-01-24 + +* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377) +* [notification: formulaire création] descend la box avec la description dans le bas du formulaire +* [notification for activity]: fix link to activity +* [notification] add "URGENT" before accompanying course with emergency = true +* [notification] add a "read more" button on system notification +* [notification] add `[Chill]` in the subject of each notification, automatically +* [notification] add a counter for notification in activity list and accompanying period list, and search results +* [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before. +* [workflow] add breadcrumb to show steps +* [popover] add popover html popup mechanism (used by workflow breadcrumb) +* [templates] improve updatedBy macro in item metadatas +* [parcours]: bug fix when comment is pinned all other comments remain in the collection (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/385) +* [workflow] + * add My workflow section with my opened subscriptions + * apply workflow on documents, accompanyingCourseWork and Evaluations +* [wopi-link] a new vue component allow to open wopi link in a fullscreen chill-themed modal + +### test release 2022-01-19 +* vuejs: add dead information on all on-the-fly person render boxes, in vis graph and other templates (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/271) +* [thirdparty] fix bug in 3rd party view: types was replaced by thirdPartyTypes +* [main] location form type: fix unmapped address field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/246) +* [activity] fix wrong import of js assets for adding and viewing documents in activity (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/83 & https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/176) +* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380) +* [forms] dynamic picker types for user/person/thirdparty types created (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/386) + +### test release 2022-01-17 + +* [main] Add editableByUser field to locationType entity, adapt the admin template and add this condition in the location-type endpoint (see https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/297) +* [main] Add mainLocation field to User entity and add it in user form type +* rewrite page which allow to select activity +* [main] Add mainLocation field to User entity and add it in user form type +* [course list in person context] show full username/label for ref +* [accompanying period work] remove the possibility to generate document from an accompanying period work +* vuejs: add validation on required fields for AddPerson, Address and Location components +* vuejs: treat 422 validation errors in locations and AddPerson components +* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380) + +## Test releases +* vuejs: add validation on required fields for AddPerson, Address and Location components +* vuejs: treat 422 validation errors in locations and AddPerson components +* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380) + +### test release 2022-01-12 + +* fix thirdparty normalizer on telephone field: https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/322 + +### test release 2022-01-11 + +* vuejs: translate in French all multiselect widgets +* [address] define address lines according postal standards for France and Belgium (default) and change AddressRender, chill_entity_render_box and AddressRenderBox.vue +* [household] change translations (champs-libres/departement-de-la-vendee/accent-suivi-developpement#109) +* [household] add address i18n in household component (champs-libres/departement-de-la-vendee/accent-suivi-developpement#158) +* [household] add on the fly i18n in household component +* [household] redirect to the household page when a household is created from a person (champs-libres/departement-de-la-vendee/accent-suivi-developpement#175) +* [household] household member editor: display alert if some members have already an household (champs-libres/departement-de-la-vendee/accent-suivi-developpement#172) +* [household] household member editor: do not add in new members if the member is included in the members of household (champs-libres/departement-de-la-vendee/accent-suivi-developpement#123) +* [household] household member editor: remove markNoAddress button (champs-libres/departement-de-la-vendee/accent-suivi-developpement#109) +* [person]: ordering fields in add person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/61) +* [person]: Add email and alt names in add person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/61) +* [accompanyingCourse] Add a delete action and delete buttons to delete a accompanying course when step = DRAFT (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/64) +* [accompanyingCourse] Add a administrative location in the accompanying course, set the user current location as default, allow to select a location in a select field and do not allow to confirm the accompanying course if location is empty. +* [accompanyingCourse] Add the administrative location in the available variables for document generation +* AddAddress: optimize loading: wait for the user finish typing; +* UserPicker: fix bug with deprecated role +* docgen: add base context + tests +* docgen: add age for person +* [household menu] fix filiation order https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/265 +* [AddAddress]: optimize loading: wait for the user finish typing; +* [UserPicker]: fix bug with deprecated role +* [docgen]: add base context + tests +* [docgen]: add age for person +* [task]: fix dropdown menu style + fix bug in singleTaskController (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/338) +* Household: fix bug when moving person on the same day (see https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/281) +* Household: show date validFrom and validTo when moving +* address reference: add index for refid +* [accompanyingCourse_work] fix styles conflicts + fix bug with remove goal (remove goals one at a time) +* [accompanyingCourse] improve masonry on resume page, add origin +* [notification] new notification interface, can be associated to AccompanyingCourse/Period, Activities. + * List notifications, show, and comment in User section + * Notify button and contextual notification box on associated objects pages +* [accompanyingCourse] add a comment for each resource associated. A modal allow to save comment. Comment is displayed in on-the-fly show modal of the accompanyingCourse context (edit page + resume page). + +### test release 2021-12-14 + +* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262) +* [vendee/person] fix typo "situation professionelle" => "situation professionnelle" +* [main] add availableForUsers condition from locationType in the location API endpoint (champs-libres/departement-de-la-vendee/accent-suivi-developpement#248) +* [main] add the current location of the user as API point + add it in the activity location list (champs-libres/departement-de-la-vendee/accent-suivi-developpement#247) +* [activity] improve show/new/edit templates, fix SEE and SEE_DETAILS acl +* [badges] create specific badge for TMS, and make person/thirdparty badges clickable with on-the-fly modal in : + * concerned groups items (activity, calendar) + * accompanyingCourseWork lists + * accompanyingCourse lists +* [acompanyingCourse] add initial comment on Resume page +* [person] create button full width (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/330) + +### test release 2021-12-11 + +* [main] add order field to civility +* [main] change address format in case the country is France, in Address render box and address normalizer +* [person] add validator for accompanying period with a test on social issues (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/76) +* [activity] fix visibility for location +* [origin] fix origin: use correctly the translatable strings + * /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');` +* [person] redirect bug fixed. +* [action] add an unrelated issue within action creation. +* [origin] fix origin: use correctly the translatable strings + * /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');` +* [main] change order of civilities in civility fixtures (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] set min attr in the minimum of children field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] add marital status date in person view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] show number of children + allow set number of children to null (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] show acceptSMS option (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] add death information in person render box in twig and vue render boxes (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262) +* [vendee/person] fix typo "situation professionelle" => "situation professionnelle" +* [accompanyingcourse_work] Changes in layout/behavior of edit form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/321) +* [badge-entity] design coherency between pills badge-person and 3 kinds of badge-thirdparty +* [AddPersons] suggestions row are clickable, not only checkbox + +### test release 2021-12-06 + +* [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316) +* [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316) +* [person search] fix bug when using birthdate after and birthdate before +* [person search] increase pertinence when lastname begins with search pattern +* [activity/actions] Améliore la cohérence du design entre + * la page résumé d'un parcours (liste d'actions récentes et liste d'activités récentes) + * la page liste des actions + * la page liste des activités (contexte personne / contexte parcours) +* [household] field to edit wheter person is titulaire of household or not removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/322) +* [activity] create work if a work with same social action is not associated to the activity +* [visgraph] improve and fix bugs on vis-network relationship graph +* [bugfix] posting of birth- and deathdate through api fixed. +* [suggestions] improve suggestions lists + +### Test release 2021-11-19 - bis + +* [household] do not allow to create two addresses on the same date +* [activity] handle case when there is no social action associated to social issue +* [activity] layout for issues / actions +* [activity][bugfix] in edit mode, the form will now load the social action list + + +### Test release 2021-11-29 + +* [person] suggest entities (person | thirdparty) when creating/editing the accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/119) +* [activity] add custom validation on the Activity class, based on what is required from the ActivityType (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/188) +* [main] translate multiselect messages when selecting/creating address +* [main] set the coordinates of the city when creating a new address OR choosing "pas d'adresse complète" +* Use the user.label in accompanying course banner, instead of username; +* fix: show validation message when closing accompanying course; +* [thirdparty] link from modal to thirdparty detail page fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/228) +* [assets] new asset to style suggestions lists (with add/remove item link) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/258) +* [accompanyingCourseWorkEdit] improves hyphenation and line breaks for long badges +* [acompanyingCourse] improve Resume page + * complete all needed informations, + * actions and activities are clickables, + * better placement with js masonry blocks on top of content area, + * https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/101 + * https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/295 +* [activity/calendar] on show page, concerned groups of persons table adapt itself to isVisibles options +* [activity] remove the "plus" button in activity list +* [activity] check ACL on activity list in person context +* [list for accompanying course in person] filter list using ACL +* [validation] toasts are displayed for errors when modifying accompanying course (generalization required). +* [period] only the user can enable confidentiality +* add an endpoint for checking permissions. See https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/232 +* [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties +* [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties +* [period] Validation added when period is confidential and confirmed -> user cannot be null. + + +## Test releases + +### Test release 2021-11-22 + +* [activity] delete admin_user_show in twig template because this route is not defined and should be defined +* [activity] suggest requestor, user and ressources for adding persons|user|3rdparty +* [calendar] suggest persons, professionals and invites for adding persons|3rdparty|user +* [activity] take into account the restrictions on person|thirdparties|users visibilities defined in ActivityType +* [main] Add currentLocation to the User entity + add a page for selecting this location + add in the user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/133) +* [activity] add user current location as default location for a new activity (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/133) +* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167) +* remove "search by phone configuration option": search by phone is now executed by default +* remplacer le classement par ordre alphabétique par un classement par ordre de pertinence, qui tient compte: + * de la présence d'une string avec le nom de la ville; + * de la similarité; + * du fait que la recherche commence par une partie du mot recherché +* ajouter la recherche par numéro de téléphone directement dans la barre de recherche et dans le formulaire recherche avancée; +* ajouter la recherche par date de naissance directement dans la barre de recherche; +* ajouter la recherche par ville dans la recherche avancée +* ajouter un lien vers le ménage dans les résultats de recherche +* ajouter l'id du parcours dans les résultats de recherche +* ajouter le demandeur dans les résultats de recherche +* ajout d'un bouton "recherche avancée" sur la page d'accueil +* [person] create an accompanying course: add client-side validation if no origin (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/210) +* [person] fix bounds for computing current person address: the new address appears immediatly +* [docgen] create a normalizer and serializer for normalization on doc format +* [person normalization] the key center is now "centers" and is an array. Empty array if no center +* [accompanyingCourse] Ability to close accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/296) +* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167) +* [list result] show all courses, except ones with period closed +* [accompanyingCourse] improve banner with small carousel to display slide social-issues or slide associated persons (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/69) + +### Test release 2021-11-15 + +* [main] fix adding multiple AddresseDeRelais (combine PickAddressType with ChillCollection) +* [person]: do not suggest the current household of the person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/51) +* [person]: display other phone numbers in view + add message in case no others phone numbers (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/184) +* unnecessary whitespace removed from person banner after person-id + double parentheses removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/290) +* [person]: delete accompanying period work, including related objects (cascade) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/36) +* [address]: Display of incomplete address adjusted. +* [household]: improve relationship graph + * add form to create/edit/delete relationship link, + * improve graph refresh mechanism + * add feature to export canvas as image (png) +* [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern; +* [person] do not ask for center any more on person creation +* [3party] do not ask for center any more on 3party creation + +## Test releases + +### Test release 2021-11-08 + +* [person]: Display the name of a user when searching after a User (TMS) +* [person]: Add civility to the person +* [person]: Various improvements on the edit person form +* [person]: Set available_languages and available_countries as parameters for use in the edit person form +* [activity] Bugfix: documents can now be added to an activity. +* [tasks] improve tasks with filter order +* [tasks] refactor singleControllerTasks: limit the number of conditions from the context +* [validations] validation of accompanying period added: no duplicate participations or resources (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/60). +* [renderbox] If gender of person is not defined, no icon is displayed instead of neuter-icon (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/129). +* [confidential information] module added to blur confidential information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/248). +* refactor `AuthorizationHelper` and `UserACLAwareRepository` to fix constructor, and separate logic for parent role helper into `ParentRoleHelper` +* [main]: filter location and locationType in backend: exclude NULL names, only active and availableToUsers +* [activity]: perform client-side validation & show/hide fields in the "new location" modal +* [person]: normalize person with CenterResolverDispatcher and handle case where center is null or multiple in PersonRenderBox +* [docstore] voter for PersonDocument and AccompanyingCourseDocument on the 2.0 way (using VoterHelperFactory) +* [docstore] add authorization check inside controller and menu +* [activity]: fix inheritance for role `ACTIVITY FULL` and add missing acl in menu +* [person] show current address in search results +* [person] show alt names in search results +* [admin]: links to activity admin section added again. +* [household]: endDate field deleted from household edit form. +* [household]: View accompanying periods of current and old household members. +* [tasks]: different layout for task list / my tasks, and fix link to tasks in alert or in warning +* [admin]: links to activity admin section added again. +* [household]: household addresses ordered by ValidFrom date and by id to show the last created address on top. +* [socialWorkAction]: display of social issue and parent issues + banner context added. +* [DBAL dependencies] Upgrade to DBAL 3.1 + +### Test release 2021-10-27 + +* [person]: delete double actions buttons on search person page +* [person]: accompanying course work: remove creation date display the list of work + handle case when end date is null +* [main]: Add new pages with a menu for managing location and location type in the admin +* [main]: Add some fixtures for location type +* [calendar]: Pass the location when transforming a calendar item (rdv) into an activity +* [calendar]: Add a user menu for "my calendar" + +### Test release 2021-10-18 + +* [3party]: french translation of contact and company +* [3party]: show parent in list +* [3party]: change color for badge "child" +* [3party]: fix address creation +* [household members editor] finalisation of editor +* [AccompanyingCourse banner]: replace translation referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/70) +* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location. +* [household]: add relationship page with dynamic data visualisation graph + +## Test releases + +### Test release 2021-10-11 + +* Address: zoom on postal code geometry + fix origin of manually entered postal code + +* in the Address vue component, order the postal code and street address by alphabetic and numeric order + +* add 3 new fields to PostalCode and adapt postal code command and fixtures + +* [Aside activity] Fixes for aside activity + + * categories with child + * fast creation buttons + * add ordering for types + +* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities; +* Improve badges behaviour with small screens; + +* [ThirdParty]: + + * third party list + * create a kind contact/institution when create a new thirdparty, and set contact embedded as kind=child; + * filter thirdparties in list + +* [FilterOrder]: add development kit for generating filter and ordering in list +* [Capitalization of names] person names are capitalized on creation, on prePersist event +* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ; +* [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ; + +### test release 2021-10-04 + +* [Household editor][UI] Update how household suggestion and addresses are picked; + + See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/80 +* [AddAddress] Handle address suggestion; +* [CenterType][Create a person] when overriding the ACL rules, allow to show a PickCenterType + when no centers are reachable by the default ACL. +* [Household] Show comment event if no address are associated with the household; +* [Person results] Add requestor into search results: + + * a badge "requestor" is shown into search results; + * periods where the person is only requestor (without participating) are also shown; + + Issues: + + * https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/13 + * https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/199 +* [Person form] "accept sms" not required: + + https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37 + https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221 + +* [Household editor] suggest only temporarily addresses; + See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/82 +* On-The-Fly modale works for showing, editing and creating person and thirdparty ; +* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ; +* [AddAddress] Handle address suggestion; +* [AddAddress][Entity address]: add a link between address and address reference; +* [Household editor] suggest household by comparing the temporary addresses from courses; + + See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/81 +* On-The-Fly modale works for showing, editing and creating person and thirdparty + + +## Test released + + + +## Stable releases + +No stable releases for v2+ + diff --git a/.changes/v2.1.0.md b/.changes/v2.1.0.md new file mode 100644 index 000000000..ade83aee0 --- /dev/null +++ b/.changes/v2.1.0.md @@ -0,0 +1,17 @@ +## v2.1.0 - 2023-06-12 + +### Feature + +* [docgen] allow to pick a third party when generating a document in context Activity, AccompanyingPeriod + +### Fixed + +* ([#111](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/111)) List of "my accompanying periods": separate the active and closed periods in two different lists, and show the inactive_long and inactive_short periods + +### Security + +* ([#105](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/105)) Rights are checked for display of 'accompanying period' tab in household menu. Rights are also checked for creation of 'accompanying period' from within household context + +### DX + +* Add methods to RegroupmentRepository and fullfill Center / Regroupment Doctrine mapping diff --git a/.changes/v2.2.0.md b/.changes/v2.2.0.md new file mode 100644 index 000000000..5371cad35 --- /dev/null +++ b/.changes/v2.2.0.md @@ -0,0 +1,12 @@ +## v2.2.0 - 2023-06-18 +### Feature +* When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document +* Add notification to accompanying period work and work's evaluation's documents +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates +### Fixed +* use the correct annotation for the association between PersonCurrentCenter and Person +* ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox +* ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter +### DX +* DQL function OVERLAPSI: simplify expression in postgresql diff --git a/.changes/v2.2.1.md b/.changes/v2.2.1.md new file mode 100644 index 000000000..c96b18585 --- /dev/null +++ b/.changes/v2.2.1.md @@ -0,0 +1,3 @@ +## v2.2.1 - 2023-06-19 +### Fixed +* ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation diff --git a/.changes/v2.2.2.md b/.changes/v2.2.2.md new file mode 100644 index 000000000..61d194b6d --- /dev/null +++ b/.changes/v2.2.2.md @@ -0,0 +1,5 @@ +## v2.2.2 - 2023-06-26 +### Fixed +* [Accompanying period comments]: order comments from the most recent to the oldest, in the list +* Api: filter social action to keep only the currently activated +* ([#82](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/82)) Fix deletion and re-creation of filiation relationship diff --git a/.changes/v2.3.0.md b/.changes/v2.3.0.md new file mode 100644 index 000000000..827a338de --- /dev/null +++ b/.changes/v2.3.0.md @@ -0,0 +1,42 @@ +## v2.3.0 - 2023-06-27 +### Feature +* ([#110](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/110)) Edit saved exports options: the saved exports options (forms, filters, aggregators) are now editable. +* ([#103](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/103)) Get an unified list of document in person and accompanying period context +* [export] Set the default date of calculation of the accompanying period's list as "today" +* Force accompanying period user history to be unique for the same period and stardate/enddate [:warning: may encounter migration issue] + + If some issue is encountered during migration, use this SQL to find the line which are in conflict, examine the problem and delete some of the concerning line +* + ```sql + -- to see the line which are in conflict with another one + SELECT o.* + FROM chill_person_accompanying_period_user_history o + JOIN chill_person_accompanying_period_user_history c ON o.id < c.id AND o.accompanyingperiod_id = c.accompanyingperiod_id + WHERE tsrange(o.startdate, o.enddate, '[)') && tsrange(c.startdate, c.enddate, '[)') + ORDER BY accompanyingperiod_id; + -- to examine line in conflict for a given accompanyingperiod_id (given by the previous query) + SELECT * FROM chill_person_accompanying_period_user_history WHERE accompanyingperiod_id = IIIIDDDD order by startdate, enddate; + ``` +* Rename label of filter in French: "parcours actif" => "parcours ouvert", and "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture" + +### Traduction francophone des principaux changements + +* Les exports enregistrés sont éditables par l'utilisateur; +* L'onglet "Document" dans les parcours et les dossiers d'usager affiche désormais les documents ajoutés à différents endroits. + + Pour les parcours, il s'agit de: + + - documents ajoutés directement dans le parcours; + - documents des échanges; + - documents des rendez-vous; + - documents des évaluations; + - documents directement ajoutés dans le dossier des usagers concernés par le parcours; + + Pour les usagers, il s'agit de: + + - documents des échanges; + - documents des parcours; + - documents des rendez-vous; + - documents des actions, des échanges, des rendez-vous, des évaluations ajoutés dans les parcours. +* Dans la liste des parcours, la date de calcul des éléments associés est "aujourd'hui" par défaut. +* Dans les exports, renommage des libellés des filtres: "parcours actif" => "parcours ouvert", et "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture" diff --git a/.changes/v2.4.0.md b/.changes/v2.4.0.md new file mode 100644 index 000000000..522957300 --- /dev/null +++ b/.changes/v2.4.0.md @@ -0,0 +1,36 @@ +## v2.4.0 - 2023-07-07 + +### Feature +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] on "filter by user working" on accompanying period, add two dates to filters intervention within a period +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add an aggregator by user's job working on a course +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add an aggregator by user's scope working on a course +* [export] on aggregator "user working on a course" +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a center aggregator for Person +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a filter on "job working on a course" +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add a filter on "scope working on a course" +* ([#121](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/121)) Create a role "See Confidential Periods", separated from the "Reassign courses" role +* ([#124](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/124)) Sync user absence / presence through microsoft outlook / graph api. + +### Fixed +* ([#116](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/116)) On the accompanying course page, open the action on view mode if the user does not have right to update them (i.e. if the accompanying period is closed) +* [export] Rename label for CurrentActionFilter (on accompanying period work) to make precision between "ouvert" and "sans date de fin" +* Force the db to have either a person_location or a address_location, and avoid to have both also internally in the entity +* [export] set rolling date on person age aggregator +* [export] fix list when a person locating a course is without address +* [export] remove unused condition on course about duration participation +* Command to subscribe on MS Graph users calendars: improve the loop to be more efficient + +### DX +* Rolling Date: can receive a null parameter + +### Traduction francophone des principaux changements + +- sur le "filtre par intervenant", ajoute deux dates pour limiter la période d'intervention; +- ajout d'un regroupement par métier des intervenants sur un parcours; +- ajout d'un regroupement par service des intervenants sur un parcours; +- ajout d'un regroupement par utilisateur intervenant sur un parcours +- ajout d'un regroupement "par centre de l'usager"; +- ajout d'un filtre "par métier intervenant sur un parcours"; +- ajout d'un filtre "par service intervenant sur un parcours"; +- création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot); +- synchronisation de l'absence des utilisateurs par microsoft graph api 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 new file mode 100644 index 000000000..0145062f8 --- /dev/null +++ b/.changie.yaml @@ -0,0 +1,39 @@ +changesDir: .changes +unreleasedDir: unreleased +headerPath: header.tpl.md +changelogPath: CHANGELOG.md +versionExt: md +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 and (.Custom.Long) (not (eq .Custom.Long "")) }} + + {{ .Custom.Long }}{{ end }} +custom: + - key: Issue + label: Issue number (on chill-bundles repository) (optional) + optional: true + type: int + minInt: 1 +body: + # allow multiline messages + block: true +kinds: + - label: Feature + auto: minor + - label: Deprecated + auto: minor + - label: Fixed + auto: patch + - label: Security + auto: patch + - label: DX + auto: patch + - label: UX + auto: patch +newlines: + afterChangelogHeader: 1 + beforeChangelogVersion: 1 + endOfVersion: 1 +envPrefix: CHANGIE_ 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 3f1d75ed5..389955c30 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 @@ -23,17 +23,16 @@ 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 stages: - Composer install - Tests + - Deploy build: stage: Composer install @@ -49,7 +48,7 @@ build: expire_in: 30 min paths: - bin - - tests/app/vendor/ + - vendor/ code_style: stage: Tests @@ -63,7 +62,7 @@ code_style: expire_in: 30 min paths: - bin - - tests/app/vendor/ + - vendor/ phpstan_tests: stage: Tests @@ -77,13 +76,14 @@ phpstan_tests: expire_in: 30 min 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/ @@ -91,7 +91,7 @@ rector_tests: expire_in: 30 min paths: - bin - - tests/app/vendor/ + - vendor/ # psalm_tests: # stage: Tests @@ -108,16 +108,25 @@ 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 paths: - bin - - tests/app/vendor/ + - vendor/ + +release: + stage: Deploy + image: registry.gitlab.com/gitlab-org/release-cli:latest + rules: + - if: $CI_COMMIT_TAG + script: + - echo "running release_job" + release: + tag_name: '$CI_COMMIT_TAG' + description: "./.changes/v$CI_COMMIT_TAG.md" 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 4b5bf98ee..9aae1c43f 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -13,6 +13,7 @@ $finder = PhpCsFixer\Finder::create(); $finder ->in(__DIR__.'/src') + ->in(__DIR__.'/utils') ->append([__FILE__]) ->exclude(['docs/', 'tests/app']) ->notPath('tests/app') @@ -90,7 +91,7 @@ $rules = array_merge( [ '@PhpCsFixer' => true, '@PhpCsFixer:risky' => false, - '@Symfony' => false, + '@Symfony' => true, '@Symfony:risky' => false, 'ordered_class_elements' => [ 'order' => [ @@ -110,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/CHANGELOG.md b/CHANGELOG.md index b2f951479..298a9af0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,286 @@ # Changelog - All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), +and is generated by [Changie](https://github.com/miniscruff/changie). -* [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for stable releases; -* date versioning for test releases -## Unreleased +## 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 +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] on "filter by user working" on accompanying period, add two dates to filters intervention within a period +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add an aggregator by user's job working on a course +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add an aggregator by user's scope working on a course +* [export] on aggregator "user working on a course" +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a center aggregator for Person +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a filter on "job working on a course" +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add a filter on "scope working on a course" +* ([#121](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/121)) Create a role "See Confidential Periods", separated from the "Reassign courses" role +* ([#124](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/124)) Sync user absence / presence through microsoft outlook / graph api. + +### Fixed +* ([#116](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/116)) On the accompanying course page, open the action on view mode if the user does not have right to update them (i.e. if the accompanying period is closed) +* [export] Rename label for CurrentActionFilter (on accompanying period work) to make precision between "ouvert" and "sans date de fin" +* Force the db to have either a person_location or a address_location, and avoid to have both also internally in the entity +* [export] set rolling date on person age aggregator +* [export] fix list when a person locating a course is without address +* [export] remove unused condition on course about duration participation +* Command to subscribe on MS Graph users calendars: improve the loop to be more efficient + +### DX +* Rolling Date: can receive a null parameter + +### Traduction francophone des principaux changements + +- sur le "filtre par intervenant", ajoute deux dates pour limiter la période d'intervention; +- ajout d'un regroupement par métier des intervenants sur un parcours; +- ajout d'un regroupement par service des intervenants sur un parcours; +- ajout d'un regroupement par utilisateur intervenant sur un parcours +- ajout d'un regroupement "par centre de l'usager"; +- ajout d'un filtre "par métier intervenant sur un parcours"; +- ajout d'un filtre "par service intervenant sur un parcours"; +- création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot); +- synchronisation de l'absence des utilisateurs par microsoft graph api + +## v2.3.0 - 2023-06-27 +### Feature +* ([#110](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/110)) Edit saved exports options: the saved exports options (forms, filters, aggregators) are now editable. +* ([#103](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/103)) Get an unified list of document in person and accompanying period context +* [export] Set the default date of calculation of the accompanying period's list as "today" +* Force accompanying period user history to be unique for the same period and stardate/enddate [:warning: may encounter migration issue] + + If some issue is encountered during migration, use this SQL to find the line which are in conflict, examine the problem and delete some of the concerning line +* + ```sql + -- to see the line which are in conflict with another one + SELECT o.* + FROM chill_person_accompanying_period_user_history o + JOIN chill_person_accompanying_period_user_history c ON o.id < c.id AND o.accompanyingperiod_id = c.accompanyingperiod_id + WHERE tsrange(o.startdate, o.enddate, '[)') && tsrange(c.startdate, c.enddate, '[)') + ORDER BY accompanyingperiod_id; + -- to examine line in conflict for a given accompanyingperiod_id (given by the previous query) + SELECT * FROM chill_person_accompanying_period_user_history WHERE accompanyingperiod_id = IIIIDDDD order by startdate, enddate; + ``` +* Rename label of filter in French: "parcours actif" => "parcours ouvert", and "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture" + +### Traduction francophone des principaux changements + +* Les exports enregistrés sont éditables par l'utilisateur; +* L'onglet "Document" dans les parcours et les dossiers d'usager affiche désormais les documents ajoutés à différents endroits. + + Pour les parcours, il s'agit de: + + - documents ajoutés directement dans le parcours; + - documents des échanges; + - documents des rendez-vous; + - documents des évaluations; + - documents directement ajoutés dans le dossier des usagers concernés par le parcours; + + Pour les usagers, il s'agit de: + + - documents des échanges; + - documents des parcours; + - documents des rendez-vous; + - documents des actions, des échanges, des rendez-vous, des évaluations ajoutés dans les parcours. +* Dans la liste des parcours, la date de calcul des éléments associés est "aujourd'hui" par défaut. +* Dans les exports, renommage des libellés des filtres: "parcours actif" => "parcours ouvert", et "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture" + +## v2.2.2 - 2023-06-26 +### Fixed +* [Accompanying period comments]: order comments from the most recent to the oldest, in the list +* Api: filter social action to keep only the currently activated +* ([#82](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/82)) Fix deletion and re-creation of filiation relationship + +## v2.2.1 - 2023-06-19 +### Fixed +* ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation + +## v2.2.0 - 2023-06-18 +### Feature +* When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document +* Add notification to accompanying period work and work's evaluation's documents +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates +### Fixed +* use the correct annotation for the association between PersonCurrentCenter and Person +* ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox +* ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter +### DX +* DQL function OVERLAPSI: simplify expression in postgresql + +## v2.1.0 - 2023-06-12 + +### Feature + +* [docgen] allow to pick a third party when generating a document in context Activity, AccompanyingPeriod + +### Fixed + +* ([#111](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/111)) List of "my accompanying periods": separate the active and closed periods in two different lists, and show the inactive_long and inactive_short periods + +### Security + +* ([#105](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/105)) Rights are checked for display of 'accompanying period' tab in household menu. Rights are also checked for creation of 'accompanying period' from within household context + +### DX + +* Add methods to RegroupmentRepository and fullfill Center / Regroupment Doctrine mapping + +## 2.0.0 + +* this is a release to relaunch our proceess of release with semantic versioning + +## Test releases + +### 2.0.0-beta3 - * [person][export] Fixed: rename the alias for `accompanying_period` to `acp` in filter associated with person * [activity][export] Feature: improve label for aliases in "Filter by activity type" * [activity][export] DX/Feature: use of an `ActivityTypeRepositoryInterface` instead of the old-style EntityRepository @@ -18,9 +288,6 @@ and this project adheres to * [person][export] Fixed: use left join for related entities in accompanying course aggregators * [workflow] Feature: allow user to copy and send manually the access link for the workflow * [workflow] Feature: show the email addresses that received an access link for the workflow - -## Test releases - ### 2.0.0-beta2 * [workflow]: Fixed: the notification is sent when the user is added to the first step. 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 b47856954..3195f732b 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "social worker" ], "require": { - "php": "^7.4|^8.2", + "php": "^8.2", "ext-json": "*", "ext-openssl": "*", "ext-redis": "*", @@ -34,6 +34,7 @@ "sensio/framework-extra-bundle": "^5.5", "spomky-labs/base64url": "^2.0", "symfony/browser-kit": "^4.4", + "symfony/clock": "^6.2", "symfony/css-selector": "^4.4", "symfony/expression-language": "^4.4", "symfony/form": "^4.4", @@ -47,7 +48,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", @@ -66,6 +66,7 @@ "fakerphp/faker": "^1.13", "jangregor/phpstan-prophecy": "^1.0", "nelmio/alice": "^3.8", + "nikic/php-parser": "^4.15", "phpspec/prophecy-phpunit": "^2.0", "phpstan/extension-installer": "^1.2", "phpstan/phpstan": "^1.9", @@ -74,7 +75,7 @@ "phpunit/phpunit": ">= 7.5", "psalm/plugin-phpunit": "^0.18.4", "psalm/plugin-symfony": "^4.0.2", - "rector/rector": "^0.15.23", + "rector/rector": "^0.17.7", "symfony/debug-bundle": "^5.1", "symfony/dotenv": "^4.4", "symfony/maker-bundle": "^1.20", @@ -96,20 +97,21 @@ "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", "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle", "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle", - "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src" + "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src", + "Chill\\Utils\\Rector\\": "utils/rector/src" } }, "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\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests", + "Chill\\Utils\\Rector\\Tests\\": "utils/rector/tests" } }, "config": { @@ -119,16 +121,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/source/_static/code/exports/BirthdateFilter.php b/docs/source/_static/code/exports/BirthdateFilter.php index 64c1d53a9..e25d8b42f 100644 --- a/docs/source/_static/code/exports/BirthdateFilter.php +++ b/docs/source/_static/code/exports/BirthdateFilter.php @@ -62,7 +62,6 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac { $builder->add('date_from', DateType::class, [ 'label' => 'Born after this date', - 'data' => new DateTime(), 'attr' => ['class' => 'datepicker'], 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', @@ -70,12 +69,15 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac $builder->add('date_to', DateType::class, [ 'label' => 'Born before this date', - 'data' => new DateTime(), 'attr' => ['class' => 'datepicker'], 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', ]); } + public function getFormDefaultData(): array + { + return ['date_from' => new DateTime(), 'date_to' => new DateTime()]; + } // here, we create a simple string which will describe the action of // the filter in the Response diff --git a/docs/source/_static/code/exports/CountPerson.php b/docs/source/_static/code/exports/CountPerson.php index 48eab8a55..a0f6931ac 100644 --- a/docs/source/_static/code/exports/CountPerson.php +++ b/docs/source/_static/code/exports/CountPerson.php @@ -36,6 +36,10 @@ class CountPerson implements ExportInterface { // this export does not add any form } + public function getFormDefaultData(): array + { + return []; + } public function getAllowedFormattersTypes() { @@ -50,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, }; } @@ -90,9 +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 function ($el) { - return $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/entity-info.rst b/docs/source/development/entity-info.rst new file mode 100644 index 000000000..72d8b70ea --- /dev/null +++ b/docs/source/development/entity-info.rst @@ -0,0 +1,203 @@ + +.. 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". + +.. _entity-info: + +Stats about event on entity in php world +######################################## + +It is necessary to be able to gather information about events for some entities: + +- when the event has been done; +- who did it; +- ... + +Those "infos" are not linked with right management, like describe in :ref:`timelines`. + + +“infos” for some stats and info about an entity +----------------------------------------------- + +Building an info means: + +- create an Entity, and map this entity to a SQL view (not a regular table); +- use the framework to build this entity dynamically. + +A framework api is built to be able to build multiple “infos” entities +through “union” views: + +- use a command ``bin/console chill:db:sync-views`` to synchronize view (create view if it does not exists, or update + views when new SQL parts are added in the UNION query. Internally, this command call a new ``ViewEntityInfoManager``, + which iterate over available views to build the SQL; +- one can create a new “view entity info” by implementing a + ``ViewEntityInfoProviderInterface`` +- this implementation of the interface is free to create another + interface for building each part of the UNION query. This interface + is created for AccompanyingPeriodInfo: + ``Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface`` + +So, converting new “events” into rows for ``AccompanyingPeriodInfo`` is +just implementing this interface! + +Implementation for AccompanyingPeriod (``AccompanyingPeriod/AccompanyingPeriodInfo``) +------------------------------------------------------------------------------------- + +A class is created for computing some statistical info for an +AccompanyingPeriod: ``AccompanyingPeriod/AccompanyingPeriodInfo``. This +contains information about “something happens”, who did it and when. + +Having those info in table answer some questions like: + +- when is the last and the first action (AccompanyingPeriodWork, + Activity, AccompanyingPeriodWorkEvaluation, …) on the period; +- who is “acting” on the period, and when is the last “action” for each + user. + +The AccompanyingPeriod info is mapped to a SQL view, not a table. The +sql view is built dynamically (see below), and gather infos from +ActivityBundle, PersonBundle, CalendarBundle, … It is possible to create +custom bundle and add info on this view. + +.. code:: php + + /** + * + * @ORM\Entity() + * @ORM\Table(name="view_chill_person_accompanying_period_info") <==== THIS IS A VIEW, NOT A TABLE + */ + class AccompanyingPeriodInfo + { + // ... + } + +Why do we need this ? +~~~~~~~~~~~~~~~~~~~~~ + +For multiple jobs in PHP world: + +- moving the accompanying period to another steps when inactive, + automatically; +- listing all the users which are intervening on the action on a new + “Liste des intervenants” page; +- filtering on exports + +Later, we will launch automatic anonymise for accompanying period and +all related entities through this information. + +How is built the SQL views which is mapped to “info” entities ? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The AccompanyingPeriodInfo entity is mapped by a SQL view (not a regular +table). + +The sql view is built dynamically, it is a SQL view like this, for now (April 2023): + +.. code:: sql + + create view view_chill_person_accompanying_period_info + (accompanyingperiod_id, relatedentity, relatedentityid, user_id, infodate, discriminator, metadata) as + SELECT w.accompanyingperiod_id, + 'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork'::text AS relatedentity, + w.id AS relatedentityid, + cpapwr.user_id, + w.enddate AS infodate, + 'accompanying_period_work_end'::text AS discriminator, + '{}'::jsonb AS metadata + FROM chill_person_accompanying_period_work w + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON w.id = cpapwr.accompanyingperiodwork_id + WHERE w.enddate IS NOT NULL + UNION + SELECT cpapw.accompanyingperiod_id, + 'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation'::text AS relatedentity, + e.id AS relatedentityid, + e.updatedby_id AS user_id, + e.updatedat AS infodate, + 'accompanying_period_work_evaluation_updated_at'::text AS discriminator, + '{}'::jsonb AS metadata + FROM chill_person_accompanying_period_work_evaluation e + JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id + WHERE e.updatedat IS NOT NULL + UNION + SELECT cpapw.accompanyingperiod_id, + 'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation'::text AS relatedentity, + e.id AS relatedentityid, + cpapwr.user_id, + e.maxdate AS infodate, + 'accompanying_period_work_evaluation_start'::text AS discriminator, + '{}'::jsonb AS metadata + FROM chill_person_accompanying_period_work_evaluation e + JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id + WHERE e.maxdate IS NOT NULL + UNION + SELECT cpapw.accompanyingperiod_id, + 'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation'::text AS relatedentity, + e.id AS relatedentityid, + cpapwr.user_id, + e.startdate AS infodate, + 'accompanying_period_work_evaluation_start'::text AS discriminator, + '{}'::jsonb AS metadata + FROM chill_person_accompanying_period_work_evaluation e + JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id + UNION + SELECT cpapw.accompanyingperiod_id, + 'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument'::text AS relatedentity, + doc.id AS relatedentityid, + doc.updatedby_id AS user_id, + doc.updatedat AS infodate, + 'accompanying_period_work_evaluation_document_updated_at'::text AS discriminator, + '{}'::jsonb AS metadata + FROM chill_person_accompanying_period_work_evaluation_document doc + JOIN chill_person_accompanying_period_work_evaluation e ON doc.accompanyingperiodworkevaluation_id = e.id + JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id + WHERE doc.updatedat IS NOT NULL + UNION + SELECT cpapw.accompanyingperiod_id, + 'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation'::text AS relatedentity, + e.id AS relatedentityid, + cpapwr.user_id, + e.maxdate AS infodate, + 'accompanying_period_work_evaluation_max'::text AS discriminator, + '{}'::jsonb AS metadata + FROM chill_person_accompanying_period_work_evaluation e + JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id + WHERE e.maxdate IS NOT NULL + UNION + SELECT w.accompanyingperiod_id, + 'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork'::text AS relatedentity, + w.id AS relatedentityid, + cpapwr.user_id, + w.startdate AS infodate, + 'accompanying_period_work_start'::text AS discriminator, + '{}'::jsonb AS metadata + FROM chill_person_accompanying_period_work w + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON w.id = cpapwr.accompanyingperiodwork_id + UNION + SELECT activity.accompanyingperiod_id, + 'Chill\ActivityBundle\Entity\Activity'::text AS relatedentity, + activity.id AS relatedentityid, + au.user_id, + activity.date AS infodate, + 'activity_date'::text AS discriminator, + '{}'::jsonb AS metadata + FROM activity + LEFT JOIN activity_user au ON activity.id = au.activity_id + WHERE activity.accompanyingperiod_id IS NOT NULL; + +As you can see, the view gather multiple SELECT queries and bind them +with UNION. + +Each SELECT query is built dynamically, through a class implementing an +interface: ``Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface``, `like +here `__ + +To add new `SELECT` query in different `UNION` parts in the sql view, create a +service and implements this interface: ``Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface``. diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst index 52c541c8e..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 @@ -35,6 +35,9 @@ As Chill rely on the `symfony `_ framework, reading the fram manual/index.rst 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/timelines.rst b/docs/source/development/timelines.rst index afabdb398..51c0a1bad 100644 --- a/docs/source/development/timelines.rst +++ b/docs/source/development/timelines.rst @@ -6,6 +6,8 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". +.. _timelines: + Timelines ********* @@ -18,24 +20,24 @@ Concept From an user point of view -------------------------- -Chill has two objectives : +Chill has two objectives : * make the administrative tasks more lightweight ; * help social workers to have all information they need to work To reach this second objective, Chill provides a special view: **timeline**. On a timeline view, information is gathered and shown on a single page, from the most recent event to the oldest one. -The information gathered is linked to a *context*. This *context* may be, for instance : +The information gathered is linked to a *context*. This *context* may be, for instance : * a person : events linked to this person are shown on the page ; * a center: events linked to a center are shown. They may concern different peoples ; -* ... +* ... In other word, the *context* is the kind of argument that will be used in the event's query. Let us recall that only the data the user has allowed to see should be shown. -.. seealso:: +.. seealso:: `The issue where the subject was first discussed `_ @@ -43,30 +45,30 @@ Let us recall that only the data the user has allowed to see should be shown. For developers -------------- -The `Main` bundle provides interfaces and services to help to build timelines. +The `Main` bundle provides interfaces and services to help to build timelines. If a bundle wants to *push* information in a timeline, it should be create a service which implements `Chill\MainBundle\Timeline\TimelineProviderInterface`, and tag is with `chill.timeline` and arguments defining the supported context (you may use multiple `chill.timeline` tags in order to support multiple context with a single service/class). -If a bundle wants to provide a new context for a timeline, the service `chill.main.timeline_builder` will helps to gather timeline's services supporting the defined context, and run queries across the models. +If a bundle wants to provide a new context for a timeline, the service `chill.main.timeline_builder` will helps to gather timeline's services supporting the defined context, and run queries across the models. .. _understanding-queries : Understanding queries ^^^^^^^^^^^^^^^^^^^^^ -Due to the fact that timelines should show only the X last events from Y differents tables, queries for a timeline may consume a lot of resources: at first on the database, and then on the ORM part, which will have to deserialize DB data to PHP classes, which may not be used if they are not part of the "last X events". +Due to the fact that timelines should show only the X last events from Y differents tables, queries for a timeline may consume a lot of resources: at first on the database, and then on the ORM part, which will have to deserialize DB data to PHP classes, which may not be used if they are not part of the "last X events". -To avoid such load on database, the objects are queried in two steps : +To avoid such load on database, the objects are queried in two steps : 1. An UNION request which gather the last X events, ordered by date. The data retrieved are the ID, the date, and a string key: a type. This type discriminates the data type. -2. The PHP objects are queried by ID, the type helps the program to link id with the kind of objects. +2. The PHP objects are queried by ID, the type helps the program to link id with the kind of objects. Those methods should ensure that only X PHP objects will be gathered and build by the ORM. What does the master timeline builder service ? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - When the service `chill.main.timeline_builder` is instanciated, the service is informed of each service taggued with `chill.timeline` tags. Then, + When the service `chill.main.timeline_builder` is instanciated, the service is informed of each service taggued with `chill.timeline` tags. Then, 1. The service build an UNION query by assembling column and tables names provided by the `fetchQuery` result ; 2. The UNION query is run, the result contains an id and a type for each row (see :ref:`above `) @@ -84,7 +86,7 @@ To push events on a timeline : Implementing the TimelineProviderInterface ------------------------------------------ -The has the following signature : +The has the following signature : .. code-block:: php @@ -92,19 +94,19 @@ The has the following signature : interface TimelineProviderInterface { - - /** - * + + /** + * * @param string $context * @param mixed[] $args the argument to the context. * @return TimelineSingleQuery * @throw \LogicException if the context is not supported */ public function fetchQuery($context, array $args); - + /** * Indicate if the result type may be handled by the service - * + * * @param string $type the key present in the SELECT query * @return boolean */ @@ -113,42 +115,42 @@ The has the following signature : /** * fetch entities from db into an associative array. The keys **MUST BE** * the id - * - * All ids returned by all SELECT queries + * + * All ids returned by all SELECT queries * (@see TimeLineProviderInterface::fetchQuery) and with the type * supported by the provider (@see TimelineProviderInterface::supportsType) * will be passed as argument. - * + * * @param array $ids an array of id * @return mixed[] an associative array of entities, with id as key */ public function getEntities(array $ids); - + /** * return an associative array with argument to render the entity * in an html template, which will be included in the timeline page - * + * * The result must have the following key : - * + * * - `template` : the template FQDN * - `template_data`: the data required by the template - * - * + * + * * Example: - * + * * ``` - * array( + * array( * 'template' => 'ChillMyBundle:timeline:template.html.twig', * 'template_data' => array( - * 'accompanyingPeriod' => $entity, - * 'person' => $args['person'] + * 'accompanyingPeriod' => $entity, + * 'person' => $args['person'] * ) * ); * ``` - * + * * `$context` and `$args` are defined by the bundle which will call the timeline - * rendering. - * + * rendering. + * * @param type $entity * @param type $context * @param array $args @@ -156,7 +158,7 @@ The has the following signature : * @throws \LogicException if the context is not supported */ public function getEntityTemplate($entity, $context, array $args); - + } @@ -176,7 +178,7 @@ The parameters should be replaced into the query by :code:`?`. They will be repl `$context` and `$args` are defined by the bundle which will call the timeline rendering. You may use them to build a different query depending on this context. -For instance, if the context is `'person'`, the args will be this array : +For instance, if the context is `'person'`, the args will be this array : .. code-block:: php @@ -197,7 +199,7 @@ You should find in the bundle documentation which contexts are arguments the bun .. note:: - We encourage to use `ClassMetaData` to define column names arguments. If you change your column names, changes will be reflected automatically during the execution of your code. + We encourage to use `ClassMetaData` to define column names arguments. If you change your column names, changes will be reflected automatically during the execution of your code. Example of an implementation : @@ -215,13 +217,13 @@ Example of an implementation : */ class TimelineReportProvider implements TimelineProviderInterface { - + /** * * @var EntityManager */ protected $em; - + public function __construct(EntityManager $em) { $this->em = $em; @@ -230,9 +232,9 @@ Example of an implementation : public function fetchQuery($context, array $args) { $this->checkContext($context); - + $metadata = $this->em->getClassMetadata('ChillReportBundle:Report'); - + return TimelineSingleQuery::fromArray([ 'id' => $metadata->getColumnName('id'), 'type' => 'report', @@ -254,11 +256,11 @@ Example of an implementation : The `supportsType` function ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This function indicate to the master `chill.main.timeline_builder` service (which orchestrate the build of UNION queries) that the service supports the type indicated in the result's array of the `fetchQuery` function. +This function indicate to the master `chill.main.timeline_builder` service (which orchestrate the build of UNION queries) that the service supports the type indicated in the result's array of the `fetchQuery` function. -The implementation of our previous example will be : +The implementation of our previous example will be : -.. code-block:: php +.. code-block:: php namespace Chill\ReportBundle\Timeline; @@ -272,7 +274,7 @@ The implementation of our previous example will be : //... /** - * + * * {@inheritDoc} */ public function supportsType($type) @@ -304,12 +306,12 @@ The results **must be** an array where the id given by the UNION query (remember { $reports = $this->em->getRepository('ChillReportBundle:Report') ->findBy(array('id' => $ids)); - + $result = array(); foreach($reports as $report) { $result[$report->getId()] = $report; } - + return $result; } @@ -318,9 +320,9 @@ The results **must be** an array where the id given by the UNION query (remember The `getEntityTemplate` function ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This is where the master service will collect information to render the entity. +This is where the master service will collect information to render the entity. -The result must be an associative array with : +The result must be an associative array with : - **template** is the FQDN of the template ; - **template_data** is an associative array where keys are the variables'names for this template, and values are the values. @@ -332,8 +334,8 @@ Example : array( 'template' => 'ChillMyBundle:timeline:template.html.twig', 'template_data' => array( - 'period' => $entity, - 'person' => $args['person'] + 'period' => $entity, + 'person' => $args['person'] ) ); @@ -349,7 +351,7 @@ Create a timeline with his own context You have to create a Controller which will execute the service `chill.main.timeline_builder`. Using the `Chill\MainBundle\Timeline\TimelineBuilder::getTimelineHTML` function, you will get an HTML representation of the timeline, which you may include with twig `raw` filter. -Example : +Example : .. code-block:: php 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 852a2a6d0..910122196 100644 --- a/docs/source/installation/index.rst +++ b/docs/source/installation/index.rst @@ -151,6 +151,7 @@ This script will : # mount into to container ./docker-php.sh + bin/console chill:db:sync-views # and load fixtures bin/console doctrine:migrations:migrate @@ -161,7 +162,7 @@ Chill will be available at ``http://localhost:8001.`` Currently, there isn't any # mount into to container ./docker-php.sh - # and load fixtures + # and load fixtures (do not this for production) bin/console doctrine:fixtures:load --purge-with-truncate There are several users available: @@ -204,8 +205,10 @@ How to create the database schema (= run migrations) ? # if a container is running ./docker-php.sh bin/console doctrine:migrations:migrate + bin/console chill:db:sync-views # if not docker-compose run --user $(id -u) php bin/console doctrine:migrations:migrate + docker-compose run --user $(id -u) php bin/console chill:db:sync-views How to read the email sent by the program ? @@ -236,6 +239,23 @@ How to open a terminal in the project # if not docker-compose run --user $(id -u) php /bin/bash +How to run cron-jobs ? +====================== + +Some command must be executed in :ref:`cron jobs `. To execute them: + +.. code-block:: bash + + # if a container is running + ./docker-php.sh + bin/console chill:cron-job:execute + # some of them are executed only during the night. So, we have to force the execution during the day: + bin/console chill:cron-job:execute 'name-of-the-cron' + # if not + docker-compose run --user $(id -u) php bin/console chill:cron-job:execute + # some of them are executed only during the night. So, we have to force the execution during the day: + docker-compose run --user $(id -u) php bin/console chill:cron-job:execute 'name-of-the-cron' + How to run composer ? ===================== diff --git a/docs/source/installation/prod.rst b/docs/source/installation/prod.rst index f51141e8d..21da15267 100644 --- a/docs/source/installation/prod.rst +++ b/docs/source/installation/prod.rst @@ -38,6 +38,14 @@ This should be adapted to your needs: * Think about how you will backup your database. Some adminsys find easier to store database outside of docker, which might be easier to administrate or replicate. +Run migrations on each update +============================= + +Every time you start a new version, you should apply update the sql schema: + +- running ``bin/console doctrine:migration:migrate`` to run sql migration; +- synchonizing sql views to the last state: ``bin/console chill:db:sync-views`` + Cron jobs ========= 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 fd7844691..eb0545702 100644 --- a/exports_alias_conventions.md +++ b/exports_alias_conventions.md @@ -18,9 +18,9 @@ These are alias conventions : | | 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 | @@ -28,6 +28,8 @@ These are alias conventions : | | 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 | diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 56b7c2228..62dbe0468 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,6 +2,7 @@ parameters: level: 5 paths: - src/ + - utils/ tmpDir: .cache/ reportUnmatchedIgnoredErrors: false excludePaths: diff --git a/phpunit.rector.xml b/phpunit.rector.xml new file mode 100644 index 000000000..f8d9d3a11 --- /dev/null +++ b/phpunit.rector.xml @@ -0,0 +1,29 @@ + + + + + utils/rector/tests + + + + + + utils/rector/src + + + 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 2c5b08bec..0dd8e355c 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,14 +19,37 @@ return static function (RectorConfig $rectorConfig): void { __DIR__ . '/src', ]); - $rectorConfig->cacheClass(\Rector\Caching\ValueObject\Storage\FileCacheStorage::class); - $rectorConfig->cacheDirectory(__DIR__.'/.cache/rector'); + $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'); // register a single rule $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); + $rectorConfig->disableParallel(); - // define sets of rules - // $rectorConfig->sets([ - // LevelSetList::UP_TO_PHP_74 - // ]); + //define sets of rules + $rectorConfig->sets([ + 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); + + // skip some path... + $rectorConfig->skip([ + // we need to discuss this: are we going to have FALSE in tests instead of an error ? + \Rector\Php71\Rector\FuncCall\CountOnNullRector::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/ChillActivityBundle.php b/src/Bundle/ChillActivityBundle/ChillActivityBundle.php index 5f872a7dc..a85ddbb75 100644 --- a/src/Bundle/ChillActivityBundle/ChillActivityBundle.php +++ b/src/Bundle/ChillActivityBundle/ChillActivityBundle.php @@ -13,6 +13,4 @@ namespace Chill\ActivityBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; -class ChillActivityBundle extends Bundle -{ -} +class ChillActivityBundle extends Bundle {} diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php index 97678af50..43c0b13bb 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,36 @@ 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 - ) { - $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; - } + 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, + ) {} /** * 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 +87,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 +98,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 +131,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 +141,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 +158,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 +222,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 +236,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 +302,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 +352,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 +396,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 +444,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 +525,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 +535,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 +572,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 +587,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 +614,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, @@ -650,8 +673,8 @@ final class ActivityController extends AbstractController throw $this->createNotFoundException('Accompanying Period not found'); } - // TODO Add permission - // $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); + // TODO Add permission + // $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); } else { throw $this->createNotFoundException('Person or Accompanying Period not found'); } diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php index 32cec74a5..6d0b825cc 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,9 +50,9 @@ 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(); @@ -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,6 +72,8 @@ class ActivityReasonCategoryController extends AbstractController /** * Lists all ActivityReasonCategory entities. + * + * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/", name="chill_activity_activityreasoncategory") */ public function indexAction() { @@ -77,20 +81,22 @@ class ActivityReasonCategoryController extends AbstractController $entities = $em->getRepository(\Chill\ActivityBundle\Entity\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,9 +105,9 @@ 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(); @@ -111,7 +117,7 @@ class ActivityReasonCategoryController extends AbstractController 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,9 +125,9 @@ 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(); @@ -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..141398cc4 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php @@ -24,15 +24,12 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; */ class ActivityReasonController extends AbstractController { - private ActivityReasonRepository $activityReasonRepository; - - public function __construct(ActivityReasonRepository $activityReasonRepository) - { - $this->activityReasonRepository = $activityReasonRepository; - } + public function __construct(private readonly 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 +37,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,9 +54,9 @@ 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(); @@ -71,7 +68,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 +76,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 +85,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,9 +109,9 @@ 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(); @@ -120,7 +121,7 @@ class ActivityReasonController extends AbstractController 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,9 +129,9 @@ 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(); @@ -143,13 +144,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 8c53ca2d9..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() @@ -50,7 +44,7 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface ->findAll(); foreach ($persons as $person) { - $activityNbr = mt_rand(0, 3); + $activityNbr = random_int(0, 3); for ($i = 0; $i < $activityNbr; ++$i) { $activity = $this->newRandomActivity($person); @@ -75,7 +69,7 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface // ->setAttendee($this->faker->boolean()) - for ($i = 0; mt_rand(0, 4) > $i; ++$i) { + for ($i = 0; random_int(0, 4) > $i; ++$i) { $reason = $this->getRandomActivityReason(); if (null !== $reason) { 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 e22f6242c..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; /** @@ -26,7 +25,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder('chill_activity'); - $rootNode = $treeBuilder->getRootNode('chill_activity'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() @@ -59,9 +58,7 @@ class Configuration implements ConfigurationInterface ->info('The number of seconds of this duration. Must be an integer.') ->cannotBeEmpty() ->validate() - ->ifTrue(static function ($data) { - return !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 9adfa057f..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; + 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 95b3c95d2..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; + 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 a6e0fc694..c14c8292b 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,11 +122,14 @@ class ActivityType /** * @ORM\Id + * * @ORM\Column(name="id", type="integer") + * * @ORM\GeneratedValue(strategy="AUTO") + * * @Groups({"docgen:read"}) */ - private ?int $id; + private ?int $id = null; /** * @ORM\Column(type="string", nullable=false, options={"default": ""}) @@ -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,10 +287,8 @@ 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)) { @@ -374,13 +384,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 +543,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..31fac3be8 100644 --- a/src/Bundle/ChillActivityBundle/EntityListener/ActivityEntityListener.php +++ b/src/Bundle/ChillActivityBundle/EntityListener/ActivityEntityListener.php @@ -15,22 +15,11 @@ 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) - { - $this->em = $em; - $this->workRepository = $workRepository; - } + public function __construct(private readonly EntityManagerInterface $em, private readonly AccompanyingPeriodWorkRepository $workRepository) {} public function persistActionToCourse(Activity $activity) { @@ -39,11 +28,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 +41,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 5c6656009..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'); } @@ -41,6 +41,11 @@ class ByActivityNumberAggregator implements AggregatorInterface // No form needed } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data) { return static function ($value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php index 89732412d..9282f92e4 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php @@ -17,21 +17,10 @@ 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 +29,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'); } @@ -58,6 +47,11 @@ class BySocialActionAggregator implements AggregatorInterface // no form } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data) { return function ($value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php index 158e87664..bbdadf4d6 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php @@ -17,21 +17,10 @@ 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 +29,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'); } @@ -58,6 +47,11 @@ class BySocialIssueAggregator implements AggregatorInterface // no form } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data) { return function ($value): string { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php deleted file mode 100644 index 2c7ec1483..000000000 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php +++ /dev/null @@ -1,89 +0,0 @@ -scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; - } - - public function addRole(): ?string - { - return null; - } - - public function alterQuery(QueryBuilder $qb, $data) - { - if (!in_array('actcreator', $qb->getAllAliases(), true)) { - $qb->leftJoin('activity.createdBy', 'actcreator'); - } - - $qb->addSelect('IDENTITY(actcreator.mainScope) AS creatorscope_aggregator'); - $qb->addGroupBy('creatorscope_aggregator'); - } - - public function applyOn(): string - { - return Declarations::ACTIVITY_ACP; - } - - public function buildForm(FormBuilderInterface $builder) - { - // no form - } - - public function getLabels($key, array $values, $data) - { - return function ($value): string { - if ('_header' === $value) { - return 'Scope'; - } - - if (null === $value || '' === $value) { - return ''; - } - - $s = $this->scopeRepository->find($value); - - return $this->translatableStringHelper->localize( - $s->getName() - ); - }; - } - - public function getQueryKeys($data): array - { - return ['creatorscope_aggregator']; - } - - public function getTitle(): string - { - return 'Group activity by creator scope'; - } -} 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/ActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php index 7cd16718e..fa658635b 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php @@ -15,26 +15,14 @@ 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 +31,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'); } @@ -61,7 +49,12 @@ class ActivityTypeAggregator implements AggregatorInterface // no form required for this aggregator } - public function getLabels($key, array $values, $data): Closure + public function getFormDefaultData(): array + { + return []; + } + + public function getLabels($key, array $values, $data): \Closure { // for performance reason, we load data from db only once $this->activityTypeRepository->findBy(['id' => $values]); diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php index 9bde692c6..61452af22 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php @@ -15,25 +15,14 @@ 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 { @@ -59,7 +48,12 @@ class ActivityUserAggregator implements AggregatorInterface // nothing to add } - public function getLabels($key, $values, $data): Closure + public function getFormDefaultData(): array + { + return []; + } + + 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 139f2743e..cc4ab9d14 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php @@ -17,19 +17,10 @@ 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) - { - $this->userRepository = $userRepository; - $this->userRender = $userRender; - } + public function __construct(private readonly UserRepositoryInterface $userRepository, private readonly UserRender $userRender) {} public function addRole(): ?string { @@ -38,7 +29,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'); } @@ -57,6 +48,11 @@ class ActivityUsersAggregator implements AggregatorInterface // nothing to add on the form } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data) { return function ($value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php index 5741a0e58..3628206ec 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php @@ -12,23 +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,23 +36,40 @@ 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) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { - // nothing to add in the form + return []; } public function getLabels($key, array $values, $data) @@ -77,11 +93,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 15da300be..bffce629f 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php @@ -12,23 +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,23 +36,40 @@ 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) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { - // nothing to add in the form + return []; } public function getLabels($key, array $values, $data) @@ -77,11 +93,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 79% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php index 69149737b..09bdab89e 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,17 +20,7 @@ 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,7 +35,7 @@ class ByCreatorAggregator implements AggregatorInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -53,6 +43,11 @@ class ByCreatorAggregator implements AggregatorInterface // no form } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data) { return function ($value): string { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php similarity index 75% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php index c3ca6d59c..13224bade 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,21 +17,10 @@ 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 +29,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,7 +39,7 @@ class ByThirdpartyAggregator implements AggregatorInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -58,6 +47,11 @@ class ByThirdpartyAggregator implements AggregatorInterface // no form } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data) { return function ($value): string { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php new file mode 100644 index 000000000..6641f0807 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php @@ -0,0 +1,103 @@ +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; + } + + 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 'Scope'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $s = $this->scopeRepository->find($value); + + return $this->translatableStringHelper->localize( + $s->getName() + ); + }; + } + + public function getQueryKeys($data): array + { + return [self::PREFIX.'_select']; + } + + public function getTitle(): string + { + 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 70% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php index b4b23dc0b..f7315140e 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 { @@ -29,14 +27,6 @@ class DateAggregator implements AggregatorInterface private const DEFAULT_CHOICE = 'year'; - private TranslatorInterface $translator; - - public function __construct( - TranslatorInterface $translator - ) { - $this->translator = $translator; - } - public function addRole(): ?string { return null; @@ -64,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)); @@ -74,7 +64,7 @@ class DateAggregator implements AggregatorInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -84,32 +74,28 @@ class DateAggregator implements AggregatorInterface 'multiple' => false, 'expanded' => true, 'empty_data' => self::DEFAULT_CHOICE, - 'data' => self::DEFAULT_CHOICE, ]); } + public function getFormDefaultData(): array + { + return ['frequency' => self::DEFAULT_CHOICE]; + } + public function getLabels($key, array $values, $data) { 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/JobScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/JobScopeAggregator.php new file mode 100644 index 000000000..e43f430d5 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/JobScopeAggregator.php @@ -0,0 +1,103 @@ +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 'Scope'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $s = $this->scopeRepository->find($value); + + return $this->translatableStringHelper->localize( + $s->getName() + ); + }; + } + + 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/LocationTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php similarity index 74% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php index ec4ce6315..da2d74f64 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,21 +17,10 @@ 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 +29,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,7 +39,7 @@ class LocationTypeAggregator implements AggregatorInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -58,6 +47,11 @@ class LocationTypeAggregator implements AggregatorInterface // no form } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data) { return function ($value): string { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php index eaccf95cb..4b1f4894e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php @@ -17,34 +17,15 @@ 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 - ) { - $this->activityReasonCategoryRepository = $activityReasonCategoryRepository; - $this->activityReasonRepository = $activityReasonRepository; - $this->translatableStringHelper = $translatableStringHelper; - } + public function __construct(protected ActivityReasonCategoryRepository $activityReasonCategoryRepository, protected ActivityReasonRepository $activityReasonRepository, protected TranslatableStringHelper $translatableStringHelper) {} public function addRole(): ?string { @@ -61,20 +42,20 @@ 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)) { + if (!\in_array('actreasons', $qb->getAllAliases(), true)) { $qb->innerJoin('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'); } } @@ -82,7 +63,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali // add the "group by" part $groupBy = $qb->getDQLPart('groupBy'); - if (count($groupBy) > 0) { + if (\count($groupBy) > 0) { $qb->addGroupBy($alias); } else { $qb->groupBy($alias); @@ -111,23 +92,18 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali ); } + public function getFormDefaultData(): array + { + return []; + } + 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'])); - } + match ($data['level']) { + 'reasons' => $this->activityReasonRepository->findBy(['id' => $values]), + 'categories' => $this->activityReasonCategoryRepository->findBy(['id' => $values]), + default => throw new \RuntimeException(sprintf("The level data '%s' is invalid.", $data['level'])), + }; return function ($value) use ($data) { if ('_header' === $value) { @@ -167,7 +143,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/SentReceivedAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php index 5f772e156..774968544 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php @@ -14,18 +14,12 @@ 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) - { - $this->translator = $translator; - } + public function __construct(private readonly TranslatorInterface $translator) {} public function addRole(): ?string { @@ -48,6 +42,11 @@ class SentReceivedAggregator implements AggregatorInterface // No form needed } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data): callable { return function (?string $value): string { @@ -67,7 +66,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 2b6919340..9cdbc183d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.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,7 +24,6 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class AvgActivityDuration implements ExportInterface, GroupedExportInterface @@ -31,13 +31,16 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface protected EntityRepository $repository; public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, ) { $this->repository = $em->getRepository(Activity::class); } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { + return []; } public function getAllowedFormattersTypes(): array @@ -58,7 +61,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; @@ -86,9 +89,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = array_map(static function ($el) { - return $el['center']; - }, $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $qb = $this->repository->createQueryBuilder('activity'); @@ -100,14 +101,16 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface $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) + '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); + return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php index 359593059..58ac6e829 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,7 +24,6 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterface @@ -31,7 +31,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac protected EntityRepository $repository; public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, ) { $this->repository = $em->getRepository(Activity::class); } @@ -41,6 +41,11 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac // TODO: Implement buildForm() method. } + public function getFormDefaultData(): array + { + return []; + } + public function getAllowedFormattersTypes(): array { return [FormatterInterface::TYPE_TABULAR]; @@ -59,7 +64,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; @@ -87,9 +92,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = array_map(static function ($el) { - return $el['center']; - }, $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $qb = $this->repository->createQueryBuilder('activity'); @@ -101,14 +104,16 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac $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) + '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); + return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php index 2dc844aa2..39c79cf1f 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,7 +24,6 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class CountActivity implements ExportInterface, GroupedExportInterface @@ -31,13 +31,16 @@ class CountActivity implements ExportInterface, GroupedExportInterface protected EntityRepository $repository; public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, ) { $this->repository = $em->getRepository(Activity::class); } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { + return []; } public function getAllowedFormattersTypes(): array @@ -58,7 +61,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; @@ -86,9 +89,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = array_map(static function ($el) { - return $el['center']; - }, $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $qb = $this->repository ->createQueryBuilder('activity') @@ -97,14 +98,16 @@ class CountActivity implements ExportInterface, GroupedExportInterface $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) + '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 activity.id) as export_count_activity'); return $qb; diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php index 6d25b4e22..45233d97f 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php @@ -12,9 +12,11 @@ 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; @@ -24,27 +26,18 @@ use Symfony\Component\Form\FormBuilderInterface; class ListActivity implements ListInterface, GroupedExportInterface { - private EntityManagerInterface $entityManager; - - private ListActivityHelper $helper; - - private TranslatableStringExportLabelHelper $translatableStringExportLabelHelper; - - public function __construct( - ListActivityHelper $helper, - EntityManagerInterface $entityManager, - TranslatableStringExportLabelHelper $translatableStringExportLabelHelper - ) { - $this->helper = $helper; - $this->entityManager = $entityManager; - $this->translatableStringExportLabelHelper = $translatableStringExportLabelHelper; - } + public function __construct(private readonly ListActivityHelper $helper, private readonly EntityManagerInterface $entityManager, private readonly TranslatableStringExportLabelHelper $translatableStringExportLabelHelper) {} public function buildForm(FormBuilderInterface $builder) { $this->helper->buildForm($builder); } + public function getFormDefaultData(): array + { + return []; + } + public function getAllowedFormattersTypes() { return $this->helper->getAllowedFormattersTypes(); @@ -52,7 +45,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 @@ -62,22 +55,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) @@ -99,7 +87,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() @@ -109,9 +97,7 @@ class ListActivity implements ListInterface, GroupedExportInterface public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = array_map(static function ($el) { - return $el['center']; - }, $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $qb = $this->entityManager->createQueryBuilder(); @@ -125,7 +111,7 @@ class ListActivity implements ListInterface, GroupedExportInterface ->andWhere( $qb->expr()->exists( 'SELECT 1 - FROM ' . PersonCenterHistory::class . ' acl_count_person_history + FROM '.PersonCenterHistory::class.' acl_count_person_history WHERE acl_count_person_history.person = person AND acl_count_person_history.center IN (:authorized_centers) ' @@ -142,9 +128,11 @@ class ListActivity implements ListInterface, GroupedExportInterface // 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; } @@ -158,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 1cf20dc5f..a8d6f10fd 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,7 +24,6 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class SumActivityDuration implements ExportInterface, GroupedExportInterface @@ -31,7 +31,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface protected EntityRepository $repository; public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, ) { $this->repository = $em->getRepository(Activity::class); } @@ -41,6 +41,11 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface // TODO: Implement buildForm() method. } + public function getFormDefaultData(): array + { + return []; + } + public function getAllowedFormattersTypes(): array { return [FormatterInterface::TYPE_TABULAR]; @@ -59,7 +64,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; @@ -87,9 +92,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = array_map(static function ($el) { - return $el['center']; - }, $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $qb = $this->repository ->createQueryBuilder('activity') @@ -101,14 +104,16 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface $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) + '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); + return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php index 2c160f3cf..dae572ba5 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,7 +24,6 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; -use LogicException; use Symfony\Component\Form\FormBuilderInterface; class SumActivityVisitDuration implements ExportInterface, GroupedExportInterface @@ -31,7 +31,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac protected EntityRepository $repository; public function __construct( - EntityManagerInterface $em + EntityManagerInterface $em, ) { $this->repository = $em->getRepository(Activity::class); } @@ -41,6 +41,11 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac // TODO: Implement buildForm() method. } + public function getFormDefaultData(): array + { + return []; + } + public function getAllowedFormattersTypes(): array { return [FormatterInterface::TYPE_TABULAR]; @@ -59,7 +64,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; @@ -87,9 +92,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = array_map(static function ($el) { - return $el['center']; - }, $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $qb = $this->repository ->createQueryBuilder('activity') @@ -101,14 +104,16 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac $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) + '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); + return $qb; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php index 4246df173..dbc7e3fde 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php @@ -19,21 +19,17 @@ 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\Form\FormBuilderInterface; class CountActivity implements ExportInterface, GroupedExportInterface { - protected ActivityRepository $activityRepository; + public function __construct(protected ActivityRepository $activityRepository) {} - public function __construct( - ActivityRepository $activityRepository - ) { - $this->activityRepository = $activityRepository; - } + public function buildForm(FormBuilderInterface $builder) {} - public function buildForm(FormBuilderInterface $builder) + public function getFormDefaultData(): array { + return []; } public function getAllowedFormattersTypes() @@ -54,7 +50,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; diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php index 5d438d3a5..50b8ace2a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php @@ -20,7 +20,6 @@ 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; @@ -30,14 +29,8 @@ 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', @@ -52,23 +45,7 @@ class ListActivity implements ListInterface, GroupedExportInterface 'person_id', ]; - protected TranslatableStringHelperInterface $translatableStringHelper; - - protected TranslatorInterface $translator; - - private ActivityRepository $activityRepository; - - public function __construct( - EntityManagerInterface $em, - TranslatorInterface $translator, - TranslatableStringHelperInterface $translatableStringHelper, - ActivityRepository $activityRepository - ) { - $this->entityManager = $em; - $this->translator = $translator; - $this->translatableStringHelper = $translatableStringHelper; - $this->activityRepository = $activityRepository; - } + public function __construct(protected EntityManagerInterface $entityManager, protected TranslatorInterface $translator, protected TranslatableStringHelperInterface $translatableStringHelper, private readonly ActivityRepository $activityRepository) {} public function buildForm(FormBuilderInterface $builder) { @@ -79,7 +56,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(); @@ -89,6 +66,11 @@ class ListActivity implements ListInterface, GroupedExportInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function getAllowedFormattersTypes() { return [FormatterInterface::TYPE_LIST]; @@ -113,7 +95,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'); }; @@ -137,13 +119,11 @@ class ListActivity implements ListInterface, GroupedExportInterface $activity = $activityRepository->find($value); - return implode(', ', array_map(function (ActivityReason $r) { - return '"' . - $this->translatableStringHelper->localize($r->getCategory()->getName()) - . ' > ' . - $this->translatableStringHelper->localize($r->getName()) - . '"'; - }, $activity->getReasons()->toArray())); + return implode(', ', array_map(fn (ActivityReason $r) => '"'. + $this->translatableStringHelper->localize($r->getCategory()->getName()) + .' > '. + $this->translatableStringHelper->localize($r->getName()) + .'"', $activity->getReasons()->toArray())); }; case 'circle_name': @@ -152,7 +132,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return 'circle'; } - return $this->translatableStringHelper->localize(json_decode($value, true)); + return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)); }; case 'type_name': @@ -161,7 +141,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return 'activity type'; } - return $this->translatableStringHelper->localize(json_decode($value, true)); + return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)); }; default: @@ -197,12 +177,10 @@ class ListActivity implements ListInterface, GroupedExportInterface public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = array_map(static function ($el) { - return $el['center']; - }, $acl); + $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.'); } @@ -226,7 +204,7 @@ class ListActivity implements ListInterface, GroupedExportInterface ->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 +277,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 050034954..6f254bb08 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php @@ -20,7 +20,6 @@ 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\Form\FormBuilderInterface; /** @@ -30,28 +29,24 @@ 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'; /** * @param string $action the stat to perform */ public function __construct( - ActivityRepository $activityRepository, - string $action = 'sum' - ) { - $this->action = $action; - $this->activityRepository = $activityRepository; - } + private readonly ActivityRepository $activityRepository, + /** + * The action for this report. + */ + protected string $action = 'sum' + ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { + return []; } public function getAllowedFormattersTypes() @@ -65,7 +60,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 @@ -76,7 +71,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; @@ -100,7 +95,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 @@ -153,7 +148,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 0e8b28ab4..75d2c3fd0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php @@ -23,54 +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 - ) { - $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; - } + 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 + ) {} public function addSelect(QueryBuilder $qb): void { @@ -85,7 +55,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,17 +66,15 @@ 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'); } - public function buildForm(FormBuilderInterface $builder) - { - } + public function buildForm(FormBuilderInterface $builder) {} public function getAllowedFormattersTypes() { @@ -115,113 +83,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); - - 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 a793f21de..31a29c2eb 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php @@ -23,17 +23,10 @@ use Symfony\Component\Form\FormBuilderInterface; class ActivityTypeFilter implements FilterInterface { - private ActivityTypeRepositoryInterface $activityTypeRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; - public function __construct( - ActivityTypeRepositoryInterface $activityTypeRepository, - TranslatableStringHelperInterface $translatableStringHelper - ) { - $this->activityTypeRepository = $activityTypeRepository; - $this->translatableStringHelper = $translatableStringHelper; - } + private readonly ActivityTypeRepositoryInterface $activityTypeRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) {} public function addRole(): ?string { @@ -44,7 +37,7 @@ class ActivityTypeFilter implements FilterInterface { $qb->andWhere( $qb->expr()->exists( - 'SELECT 1 FROM ' . Activity::class . ' act_type_filter_activity + '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' ) ); @@ -61,17 +54,19 @@ class ActivityTypeFilter implements FilterInterface $builder->add('accepted_activitytypes', EntityType::class, [ 'class' => ActivityType::class, 'choices' => $this->activityTypeRepository->findAllActive(), - 'choice_label' => function (ActivityType $aty) { - return - ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()) . ' > ' : '') - . - $this->translatableStringHelper->localize($aty->getName()); - }, + 'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()).' > ' : '') + . + $this->translatableStringHelper->localize($aty->getName()), 'multiple' => true, 'expanded' => true, ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $types = []; @@ -80,7 +75,7 @@ class ActivityTypeFilter implements FilterInterface $types[] = $this->translatableStringHelper->localize($aty->getName()); } - return ['Filtered by activity types: only %activitytypes%', [ + return ['export.filter.activity.acp_by_activity_type.acp_containing_at_least_one_%activitytypes%', [ '%activitytypes%' => implode(', ', $types), ]]; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php index d0c1b0fc7..13349baa5 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php @@ -18,16 +18,10 @@ 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) - { - $this->actionRender = $actionRender; - } + public function __construct(private readonly SocialActionRender $actionRender) {} public function addRole(): ?string { @@ -36,7 +30,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'); } @@ -61,6 +55,11 @@ class BySocialActionFilter implements FilterInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $actions = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php index bbb882a65..bef40290e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php @@ -18,16 +18,10 @@ 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) - { - $this->issueRender = $issueRender; - } + public function __construct(private readonly SocialIssueRender $issueRender) {} public function addRole(): ?string { @@ -36,7 +30,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'); } @@ -61,6 +55,11 @@ class BySocialIssueFilter implements FilterInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $issues = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php index 570f42ae0..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,7 +45,12 @@ class HasNoActivityFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { - //no form needed + // no form needed + } + + public function getFormDefaultData(): array + { + return []; } public function describeAction($data, $format = 'string'): array 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..2d3282ad1 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php @@ -0,0 +1,89 @@ +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 1906db75e..000000000 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php +++ /dev/null @@ -1,96 +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' => function (Scope $s) { - return $this->translatableStringHelper->localize( - $s->getName() - ); - }, - 'multiple' => true, - 'expanded' => true, - ]); - } - - 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 f2216c929..a67d0155d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php @@ -27,17 +27,7 @@ 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 { @@ -80,11 +70,9 @@ class ActivityDateFilter implements FilterInterface $builder ->add('date_from', PickRollingDateType::class, [ 'label' => 'Activities after this date', - 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), ]) ->add('date_to', PickRollingDateType::class, [ 'label' => 'Activities before this date', - 'data' => new RollingDate(RollingDate::T_TODAY), ]); $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { @@ -102,14 +90,14 @@ class ActivityDateFilter implements FilterInterface if (null === $date_from) { $form->get('date_from')->addError(new FormError( $this->translator->trans('This field ' - . 'should not be empty') + .'should not be empty') )); } if (null === $date_to) { $form->get('date_to')->addError(new FormError( $this->translator->trans('This field ' - . 'should not be empty') + .'should not be empty') )); } @@ -120,14 +108,19 @@ class ActivityDateFilter implements FilterInterface ) { $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') + .'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)]; + } + public function describeAction($data, $format = 'string') { return [ diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php index d1758039a..43f9ba0da 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php @@ -22,21 +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 - ) { - $this->translatableStringHelper = $translatableStringHelper; - $this->activityTypeRepository = $activityTypeRepository; - } + protected TranslatableStringHelperInterface $translatableStringHelper, + protected ActivityTypeRepositoryInterface $activityTypeRepository + ) {} public function addRole(): ?string { @@ -61,12 +52,9 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter $builder->add('types', EntityType::class, [ 'choices' => $this->activityTypeRepository->findAllActive(), 'class' => ActivityType::class, - 'choice_label' => function (ActivityType $aty) { - return - ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()) . ' > ' : '') - . - $this->translatableStringHelper->localize($aty->getName()); - }, + 'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()).' > ' : '') + . + $this->translatableStringHelper->localize($aty->getName()), 'group_by' => function (ActivityType $type) { if (!$type->hasCategory()) { return null; @@ -82,6 +70,11 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string') { // collect all the reasons'name used in this filter in one array @@ -102,7 +95,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 2f6cd8462..56285c026 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php @@ -20,12 +20,7 @@ use Symfony\Component\Form\FormBuilderInterface; class ActivityUsersFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) - { - $this->userRender = $userRender; - } + public function __construct(private readonly UserRender $userRender) {} public function addRole(): ?string { @@ -37,8 +32,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); @@ -57,6 +52,11 @@ class ActivityUsersFilter implements FilterInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string') { $users = []; 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 322393f32..f75c5a817 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,12 +20,7 @@ use Symfony\Component\Form\FormBuilderInterface; class ByCreatorFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) - { - $this->userRender = $userRender; - } + public function __construct(private readonly UserRender $userRender) {} public function addRole(): ?string { @@ -43,7 +38,7 @@ class ByCreatorFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -53,6 +48,11 @@ class ByCreatorFilter implements FilterInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $users = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php similarity index 82% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php index b79c2ca10..b74be2552 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,18 +22,13 @@ 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) - { - $this->translator = $translator; - } + public function __construct(private readonly TranslatorInterface $translator) {} public function addRole(): ?string { @@ -58,7 +53,7 @@ class EmergencyFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -68,10 +63,14 @@ class EmergencyFilter implements FilterInterface 'multiple' => false, 'expanded' => true, 'empty_data' => self::DEFAULT_CHOICE, - 'data' => self::DEFAULT_CHOICE, ]); } + public function getFormDefaultData(): array + { + return ['accepted_emergency' => self::DEFAULT_CHOICE]; + } + public function describeAction($data, $format = 'string'): array { return [ diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php similarity index 90% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php index 3d69d1633..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) @@ -47,6 +46,11 @@ class LocationFilter implements FilterInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $locations = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php similarity index 81% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php index 5fe928b6c..771dfca30 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,16 +18,10 @@ 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) - { - $this->translatableStringHelper = $translatableStringHelper; - } + public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} public function addRole(): ?string { @@ -36,7 +30,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,17 +49,22 @@ 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 []; + } + public function describeAction($data, $format = 'string'): array { $types = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php index c55d579e4..3f71add31 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php @@ -17,29 +17,15 @@ 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\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 +38,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'); } @@ -83,11 +69,16 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string') { // collect all the reasons'name used in this filter in one array $reasonsNames = array_map( - fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"', + fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"', $this->activityReasonRepository->findBy(['id' => $data['reasons']->toArray()]) ); @@ -106,7 +97,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 e3c85fe9c..eb2312c0e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php @@ -16,42 +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 - ) { - $this->translatableStringHelper = $translatableStringHelper; - $this->activityReasonRepository = $activityReasonRepository; - $this->translator = $translator; - } + private TranslatableStringHelper $translatableStringHelper, + private ActivityReasonRepository $activityReasonRepository, + private RollingDateConverterInterface $rollingDateConverter, + ) {} public function addRole(): ?string { @@ -62,133 +43,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', - 'data' => new DateTime(), - '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', - 'data' => new DateTime(), - '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()), - 'data' => $this->activityReasonRepository->findAll(), - 'multiple' => true, - 'expanded' => false, - 'label' => 'Activity reasons for those activities', - ]); + 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', + ]); + } + } - $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') - )); - } - } - }); + public function getFormDefaultData(): array + { + 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'] ) ), @@ -198,13 +143,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/ACPFilters/SentReceivedFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php similarity index 88% rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php index 8daa7a781..640f33592 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; @@ -29,12 +29,7 @@ class SentReceivedFilter implements FilterInterface private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT; - private TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) - { - $this->translator = $translator; - } + public function __construct(private readonly TranslatorInterface $translator) {} public function addRole(): ?string { @@ -59,7 +54,7 @@ class SentReceivedFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -69,10 +64,14 @@ class SentReceivedFilter implements FilterInterface 'multiple' => false, 'expanded' => true, 'empty_data' => self::DEFAULT_CHOICE, - 'data' => self::DEFAULT_CHOICE, ]); } + public function getFormDefaultData(): array + { + return ['accepted_sentreceived' => self::DEFAULT_CHOICE]; + } + public function describeAction($data, $format = 'string'): array { $sentreceived = array_flip(self::CHOICES)[$data['accepted_sentreceived']]; 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 6350f3ace..6e6b745b9 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,12 +21,7 @@ use Symfony\Component\Form\FormBuilderInterface; class UserFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) - { - $this->userRender = $userRender; - } + public function __construct(private readonly UserRender $userRender) {} public function addRole(): ?string { @@ -51,7 +46,7 @@ class UserFilter implements FilterInterface public function applyOn(): string { - return Declarations::ACTIVITY_ACP; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -62,6 +57,11 @@ class UserFilter implements FilterInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $users = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UserScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UserScopeFilter.php new file mode 100644 index 000000000..d205e2d30 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UserScopeFilter.php @@ -0,0 +1,112 @@ +leftJoin('activity.user', "{$p}_user") // createdBy ? cfr translation + ->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, + '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/UsersJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php index b52ef441c..b65994b9d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php @@ -13,6 +13,7 @@ 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\Templating\TranslatableStringHelperInterface; @@ -22,12 +23,11 @@ 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 + ) {} public function addRole(): ?string { @@ -36,14 +36,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,17 +64,18 @@ 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, - ]); + $builder + ->add('jobs', EntityType::class, [ + 'class' => UserJob::class, + '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( @@ -74,8 +86,15 @@ class UsersJobFilter implements FilterInterface ]]; } + 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 61b12264e..4c4a83c20 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php @@ -14,6 +14,7 @@ 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; @@ -23,17 +24,12 @@ 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 - ) { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; - } + private readonly ScopeRepositoryInterface $scopeRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) {} public function addRole(): ?string { @@ -42,35 +38,47 @@ 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, - ]); + $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( @@ -81,8 +89,15 @@ class UsersScopeFilter implements FilterInterface ]]; } - 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/ActivityReasonCategoryType.php b/src/Bundle/ChillActivityBundle/Form/ActivityReasonCategoryType.php index b8e03d2c7..3a0f2a318 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityReasonCategoryType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityReasonCategoryType.php @@ -32,7 +32,7 @@ class ActivityReasonCategoryType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ - 'data_class' => 'Chill\ActivityBundle\Entity\ActivityReasonCategory', + 'data_class' => \Chill\ActivityBundle\Entity\ActivityReasonCategory::class, ]); } diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index 5dfd756f9..03a21a5b7 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -34,12 +34,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 +49,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 +87,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 +95,16 @@ class ActivityType extends AbstractType ]); } - /** @var ? \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingPeriod */ + /** @var \Chill\PersonBundle\Entity\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 +130,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( @@ -211,17 +185,13 @@ class ActivityType extends AbstractType 'required' => $activityType->isRequired('attendee'), 'expanded' => true, 'class' => ActivityPresence::class, - 'choice_label' => function (ActivityPresence $activityPresence) { - return $this->translatableStringHelper->localize($activityPresence->getName()); - }, - 'query_builder' => static function (EntityRepository $er) { - return $er->createQueryBuilder('a') - ->where('a.active = true'); - }, + 'choice_label' => fn (ActivityPresence $activityPresence) => $this->translatableStringHelper->localize($activityPresence->getName()), + 'query_builder' => static fn (EntityRepository $er) => $er->createQueryBuilder('a') + ->where('a.active = true'), ]); } - 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'), @@ -346,7 +316,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( @@ -357,9 +327,7 @@ class ActivityType extends AbstractType return (string) $location->getId(); }, - function (?string $id): ?Location { - return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]); - } + fn (?string $id): ?Location => $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]) )); } @@ -399,18 +367,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() === null ? - DateTime::createFromFormat('U', '300') : - $formEvent->getData(); + $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( @@ -435,8 +401,8 @@ 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']); } diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php b/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php index b27fa883e..8e8ae51f7 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php @@ -25,12 +25,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class ActivityTypeType extends AbstractType { - private TranslatableStringHelper $translatableStringHelper; - - public function __construct(TranslatableStringHelper $translatableStringHelper) - { - $this->translatableStringHelper = $translatableStringHelper; - } + public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -45,9 +40,7 @@ class ActivityTypeType extends AbstractType ]) ->add('category', EntityType::class, [ 'class' => ActivityTypeCategory::class, - 'choice_label' => function (ActivityTypeCategory $activityTypeCategory) { - return $this->translatableStringHelper->localize($activityTypeCategory->getName()); - }, + 'choice_label' => fn (ActivityTypeCategory $activityTypeCategory) => $this->translatableStringHelper->localize($activityTypeCategory->getName()), ]) ->add('ordering', NumberType::class, [ 'required' => true, @@ -63,8 +56,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 a951ef940..be1e372f1 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php @@ -24,30 +24,18 @@ 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 - ) { - $this->activityReasonRepository = $activityReasonRepository; - $this->reasonRender = $reasonRender; - $this->translatableStringHelper = $translatableStringHelper; - } + private readonly ActivityReasonRepository $activityReasonRepository, + private readonly ActivityReasonRender $reasonRender, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) {} public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults( [ 'class' => ActivityReason::class, - 'choice_label' => function (ActivityReason $choice) { - return $this->reasonRender->renderString($choice, []); - }, + 'choice_label' => fn (ActivityReason $choice) => $this->reasonRender->renderString($choice, []), 'group_by' => function (ActivityReason $choice): ?string { if (null !== $category = $choice->getCategory()) { return $this->translatableStringHelper->localize($category->getName()); diff --git a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php index 96dabe008..ee1417bfb 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php @@ -23,34 +23,19 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class TranslatableActivityReasonCategoryType extends AbstractType { - private TranslatableStringHelperInterface $translatableStringHelper; - - private TranslatorInterface $translator; - - public function __construct(TranslatableStringHelperInterface $translatableStringHelper, TranslatorInterface $translator) - { - $this->translatableStringHelper = $translatableStringHelper; - $this->translator = $translator; - } + public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatorInterface $translator) {} public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults( [ 'class' => ActivityReasonCategory::class, - 'choice_label' => function (ActivityReasonCategory $category) { - return $this->translatableStringHelper->localize($category->getName()) - . (!$category->getActive() ? ' (' . $this->translator->trans('inactive') . ')' : ''); - }, + 'choice_label' => fn (ActivityReasonCategory $category) => $this->translatableStringHelper->localize($category->getName()) + .(!$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 4a1d4bfa7..5c77e500d 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityType.php @@ -20,17 +20,7 @@ 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) { @@ -39,9 +29,7 @@ class TranslatableActivityType extends AbstractType 'class' => ActivityType::class, 'active_only' => true, 'choices' => $this->activityTypeRepository->findAllActive(), - 'choice_label' => function (ActivityType $type) { - return $this->translatableStringHelper->localize($type->getName()); - }, + 'choice_label' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getName()), ] ); } diff --git a/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php index 9884450ec..b4990c0e3 100644 --- a/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -23,17 +23,7 @@ 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..0afe11cfc 100644 --- a/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php @@ -18,14 +18,9 @@ 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) - { - $this->security = $security; - } + public function __construct(private 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 03a1b09ab..e76aaf5ee 100644 --- a/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php @@ -21,25 +21,9 @@ use Symfony\Contracts\Translation\TranslatorInterface; /** * @implements LocalMenuBuilderInterface */ -final class PersonMenuBuilder implements LocalMenuBuilderInterface +final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface { - /** - * @var AuthorizationCheckerInterface - */ - protected $authorizationChecker; - - /** - * @var TranslatorInterface - */ - protected $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) { diff --git a/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php b/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php index ab26da81d..8eb219fd2 100644 --- a/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php +++ b/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php @@ -16,14 +16,9 @@ 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) - { - $this->activityRepository = $activityRepository; - } + public function __construct(private ActivityRepository $activityRepository) {} public function getTemplate(Notification $notification, array $options = []): string { @@ -40,6 +35,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 185e008eb..231ad5432 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -18,65 +18,206 @@ 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 - ) { - $this->authorizationHelper = $authorizationHelper; - $this->centerResolverDispatcher = $centerResolverDispatcher; - $this->tokenStorage = $tokenStorage; - $this->repository = $repository; - $this->em = $em; - $this->security = $security; + private AuthorizationHelperForCurrentUserInterface $authorizationHelper, + private CenterResolverManagerInterface $centerResolverManager, + private ActivityRepository $repository, + private EntityManagerInterface $em, + private Security $security, + private RequestStack $requestStack, + ) {} + + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int + { + $qb = $this->buildBaseQuery($filters); + + $qb + ->select('COUNT(a)') + ->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period); + + return $qb->getQuery()->getSingleScalarResult(); } - public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array + public function countByPerson(Person $person, 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 = $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 @@ -159,25 +300,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 +378,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 +394,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 +415,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,16 +435,14 @@ 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 function (Scope $scope) { - return $scope->getId(); - }, + static fn (Scope $scope) => $scope->getId(), $reachableScopes ); @@ -271,7 +457,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 new file mode 100644 index 000000000..99e75f2da --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -0,0 +1,187 @@ +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 + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $activityMetadata = $this->em->getClassMetadata(Activity::class); + + $query = new FetchQuery( + PersonActivityGenericDocProvider::KEY, + sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), + sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), + sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) + ); + + $query->addJoinClause( + 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id' + ); + + $query->addJoinClause( + 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' + ); + + $query->addWhereClause( + sprintf('activity.%s = ?', $activityMetadata->getSingleAssociationJoinColumnName('person')), + [$person->getId()], + [Types::INTEGER] + ); + + return $this->addWhereClauses($query, $startDate, $endDate, $content); + } + + 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); + + $query = new FetchQuery( + AccompanyingPeriodActivityGenericDocProvider::KEY, + sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), + sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), + sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) + ); + + $query->addJoinClause( + 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id' + ); + + $query->addJoinClause( + 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' + ); + + // add documents of activities from parcours context + $or = []; + $orParams = []; + $orTypes = []; + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $or[] = sprintf( + '(activity.%s = ? AND stored_obj.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', + $activityMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), + $storedObjectMetadata->getColumnName('createdAt') + ); + $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), + \DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : \DateTimeImmutable::createFromInterface($participation->getEndDate())]; + $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; + } + + if ([] === $or) { + $query->addWhereClause('TRUE = FALSE'); + + return $query; + } + + $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); + + return $this->addWhereClauses($query, $startDate, $endDate, $content); + } + + private function addWhereClauses(FetchQuery $query, \DateTimeImmutable $startDate = null, \DateTimeImmutable $endDate = null, string $content = null): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('stored_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('stored_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content and '' !== $content) { + $query->addWhereClause( + 'stored_obj.title ilike ?', + ['%'.$content.'%'], + [Types::STRING] + ); + } + + return $query; + } + + private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery + { + $activityMetadata = $this->em->getClassMetadata(Activity::class); + + $reachableScopes = []; + + foreach ($this->centerResolverManager->resolveCenters($person) as $center) { + $reachableScopes = [ + ...$reachableScopes, + ...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center), + ]; + } + + if ([] === $reachableScopes) { + $fetchQuery->addWhereClause('FALSE = TRUE'); + + return $fetchQuery; + } + + $fetchQuery->addWhereClause( + sprintf( + 'activity.%s IN (%s)', + $activityMetadata->getSingleAssociationJoinColumnName('scope'), + implode(', ', array_fill(0, count($reachableScopes), '?')) + ), + array_map(static fn (Scope $s) => $s->getId(), $reachableScopes), + array_fill(0, count($reachableScopes), Types::INTEGER) + ); + + return $fetchQuery; + } +} diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php new file mode 100644 index 000000000..dcd45c016 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php @@ -0,0 +1,36 @@ +repository->findAll(); } - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array { return $this->findBy($criteria, $orderBy, $limit, $offset); } diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php index 228d70856..d2daa1d5d 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php @@ -25,7 +25,7 @@ interface ActivityPresenceRepositoryInterface /** * @return array|ActivityPresence[] */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array; + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array; public function findOneBy(array $criteria): ?ActivityPresence; 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..6f7453a74 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) { @@ -48,7 +48,7 @@ final class ActivityTypeRepository implements ActivityTypeRepositoryInterface /** * @return array|ActivityType[] */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } 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/public/chill/chillactivity.scss b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss index d70bd28c3..13de8dfc0 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss +++ b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss @@ -1,5 +1,7 @@ // Access to Bootstrap variables and mixins @import '~ChillMainAssets/module/bootstrap/shared'; +@import '~ChillPersonAssets/chill/scss/mixins.scss'; +@import 'bootstrap/scss/_badge.scss'; //// ACTIVITY CREATION // first step: select type page @@ -96,3 +98,25 @@ li.document-list-item { justify-content: space-between; margin-bottom: 0.3rem; } + +.badge-activity-type { + display: inline-block; + background-color: #f3f3f3; + + .title_label { + @include chill_badge(#9acd32); + } + + .title_action { + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + margin-right: 1rem; + + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + } +} 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/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/GenericDoc/activity_document.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig new file mode 100644 index 000000000..5b9547675 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig @@ -0,0 +1,83 @@ +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} +{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} +{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} + +{% set person_id = null %} +{% if activity.person %} + {% set person_id = activity.person.id %} +{% endif %} + +{% set accompanying_course_id = null %} +{% if activity.accompanyingPeriod %} + {% set accompanying_course_id = activity.accompanyingPeriod.id %} +{% endif %} + +
      +
      +
      + {% if document.isPending %} +
      {{ 'docgen.Doc generation is pending'|trans }}
      + {% elseif document.isFailure %} +
      {{ 'docgen.Doc generation failed'|trans }}
      + {% endif %} + +
      + {% if activity.accompanyingPeriod is not null and context == 'person' %} + + {{ activity.accompanyingPeriod.id }} +   + {% endif %} +
      + + + {{ activity.type.name | localize_translatable_string }} + {% if activity.emergency %} + {{ 'Emergency'|trans|upper }} + {% endif %} + +
      +
      +
      + {{ document.title|chill_print_or_message("No title") }} +
      + {% if document.hasTemplate %} +
      +

      {{ document.template.name|localize_translatable_string }}

      +
      + {% endif %} +
      + +
      +
      +
      + {{ document.createdAt|format_date('short') }} +
      +
      +
      +
      + + +
      +
      + {{ mmm.createdBy(document) }} +
      +
        + {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %} +
      • + {{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }} +
      • + {% endif %} + {% if is_granted('CHILL_ACTIVITY_SEE', activity)%} +
      • + +
      • + {% endif %} + {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %} +
      • + +
      • + {% endif %} +
      + +
      +
      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 ad570a8a4..46e466ae1 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php @@ -18,12 +18,14 @@ 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; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Templating\Entity\PersonRenderInterface; +use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; +use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -39,41 +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; - public function __construct( - DocumentCategoryRepository $documentCategoryRepository, - NormalizerInterface $normalizer, - TranslatableStringHelperInterface $translatableStringHelper, - EntityManagerInterface $em, - PersonRenderInterface $personRender, - PersonRepository $personRepository, - TranslatorInterface $translator, - BaseContextData $baseContextData - ) { - $this->documentCategoryRepository = $documentCategoryRepository; - $this->normalizer = $normalizer; - $this->translatableStringHelper = $translatableStringHelper; - $this->em = $em; - $this->personRender = $personRender; - $this->personRepository = $personRepository; - $this->translator = $translator; - $this->baseContextData = $baseContextData; - } + 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 + ) {} public function adminFormReverseTransform(array $data): array { @@ -89,6 +67,8 @@ class ActivityContext implements 'person1Label' => $data['person1Label'] ?? $this->translator->trans('docgen.person 1'), 'person2' => $data['person2'] ?? false, 'person2Label' => $data['person2Label'] ?? $this->translator->trans('docgen.person 2'), + 'thirdParty' => $data['thirdParty'] ?? false, + 'thirdPartyLabel' => $data['thirdPartyLabel'] ?? $this->translator->trans('thirdParty'), ]; } @@ -118,6 +98,14 @@ class ActivityContext implements ->add('person2Label', TextType::class, [ 'label' => 'person 2 label', 'required' => true, + ]) + ->add('thirdParty', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.Ask for thirdParty', + ]) + ->add('thirdPartyLabel', TextType::class, [ + 'label' => 'docgen.thirdParty label', + 'required' => true, ]); } @@ -134,17 +122,29 @@ class ActivityContext implements $builder->add($key, EntityType::class, [ 'class' => Person::class, 'choices' => $persons, - 'choice_label' => function (Person $p) { - return $this->personRender->renderString($p, []); - }, + 'choice_label' => fn (Person $p) => $this->personRender->renderString($p, []), 'multiple' => false, 'required' => false, 'expanded' => true, - 'label' => $options[$key . 'Label'], + 'label' => $options[$key.'Label'], 'placeholder' => $this->translator->trans('Any person selected'), ]); } } + + $thirdParties = $entity->getThirdParties(); + if ($options['thirdParty'] ?? false) { + $builder->add('thirdParty', EntityType::class, [ + 'class' => ThirdParty::class, + 'choices' => $thirdParties, + 'choice_label' => fn (ThirdParty $p) => $this->thirdPartyRender->renderString($p, []), + 'multiple' => false, + 'required' => false, + 'expanded' => true, + 'label' => $options['thirdPartyLabel'], + 'placeholder' => $this->translator->trans('Any third party selected'), + ]); + } } public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array @@ -159,6 +159,12 @@ class ActivityContext implements } } + if (null !== ($id = ($data['thirdParty'] ?? null))) { + $denormalized['thirdParty'] = $this->thirdPartyRepository->find($id); + } else { + $denormalized['thirdParty'] = null; + } + return $denormalized; } @@ -167,9 +173,11 @@ class ActivityContext implements $normalized = []; foreach (['mainPerson', 'person1', 'person2'] as $k) { - $normalized[$k] = null === $data[$k] ? null : $data[$k]->getId(); + $normalized[$k] = ($data[$k] ?? null)?->getId(); } + $normalized['thirdParty'] = ($data['thirdParty'] ?? null)?->getId(); + return $normalized; } @@ -198,6 +206,13 @@ class ActivityContext implements } } + if ($options['thirdParty']) { + $data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'], 'docgen', [ + 'docgen:expects' => ThirdParty::class, + 'groups' => 'docgen:read', + ]); + } + return $data; } @@ -237,7 +252,7 @@ class ActivityContext implements { $options = $template->getOptions(); - return $options['mainPerson'] || $options['person1'] || $options['person2']; + 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 b2272dd7b..221d1f4b2 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -32,13 +32,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,45 +46,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, - ) { - $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; - } + 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 + ) {} public function adminFormReverseTransform(array $data): array { @@ -140,30 +110,37 @@ class ListActivitiesByAccompanyingPeriodContext implements return $normalized; } + /** + * @return list + */ private function filterActivitiesByUser(array $activities, User $user): array { - return array_filter( - $activities, - function ($activity) use ($user) { - $activityUsernames = array_map(static function ($user) { - return $user['username']; - }, $activity['users'] ?? []); - return in_array($user->getUsername(), $activityUsernames, true); - } + return array_values( + array_filter( + $activities, + function ($activity) use ($user) { + $activityUsernames = array_map(static fn ($user) => $user['username'], $activity['users'] ?? []); + + return \in_array($user->getUsername(), $activityUsernames, true); + } + ) ); } + /** + * @return list + */ private function filterWorksByUser(array $works, User $user): array { - return array_filter( - $works, - function ($work) use ($user) { - $workUsernames = array_map(static function ($user) { - return $user['username']; - }, $work['referrers'] ?? []); + return array_values( + array_filter( + $works, + 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); + } + ) ); } @@ -185,6 +162,7 @@ class ListActivitiesByAccompanyingPeriodContext implements if ($myWorksOnly && isset($contextGenerationData['creator'])) { $data['course']['works'] = $this->filterWorksByUser($data['course']['works'], $contextGenerationData['creator']); } + return $data; } @@ -238,7 +216,7 @@ class ListActivitiesByAccompanyingPeriodContext implements $activity = $row[0]; $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']) { @@ -251,8 +229,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/EntityInfo/AccompanyingPeriodInfoQueryPart/ActivityUsersDateQueryPartForAccompanyingPeriodInfo.php b/src/Bundle/ChillActivityBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/ActivityUsersDateQueryPartForAccompanyingPeriodInfo.php new file mode 100644 index 000000000..bb70d6e87 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/ActivityUsersDateQueryPartForAccompanyingPeriodInfo.php @@ -0,0 +1,64 @@ +em->getClassMetadata(StoredObject::class); + $activityMetadata = $this->em->getClassMetadata(Activity::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', doc_obj.%s, 'activity_id', activity.%s)", $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), + 'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'), + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj' + ); + + $query->addJoinClause( + 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = doc_obj.id' + ); + + $query->addJoinClause( + 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' + ); + + $query->addWhereClause( + 'activity.accompanyingperiod_id = ?', + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + 'doc_obj.title ilike ?', + ['%'.$content.'%'], + [Types::STRING] + ); + } + + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(ActivityVoter::SEE, $accompanyingPeriod); + } + + public function isAllowedForPerson(Person $person): bool + { + 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 + { + 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 new file mode 100644 index 000000000..b84345375 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php @@ -0,0 +1,44 @@ +personActivityDocumentACLAwareRepository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext( + $person, + $startDate, + $endDate, + $content + ); + } + + 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 new file mode 100644 index 000000000..93649cea8 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -0,0 +1,43 @@ +key || PersonActivityGenericDocProvider::KEY === $genericDocDTO->key; + } + + public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string + { + return '@ChillActivity/GenericDoc/activity_document.html.twig'; + } + + public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array + { + return [ + 'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']), + 'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']), + 'context' => $genericDocDTO->getContext(), + ]; + } +} 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 8e2c219f0..38aa49cdf 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); } @@ -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,32 +349,27 @@ 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( - array_map(static function ($s) { - return $s->getId(); - }, $reachableScopesDelete), - array_map(static function ($s) { - return $s->getId(); - }, $reachableScopesUpdate) + array_map(static fn ($s) => $s->getId(), $reachableScopesDelete), + 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/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/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/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/JobScopeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/JobScopeAggregatorTest.php new file mode 100644 index 000000000..69d511ace --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/JobScopeAggregatorTest.php @@ -0,0 +1,61 @@ +aggregator = self::$container->get(JobScopeAggregator::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/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/ActivityReasonAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php index 0a61e983f..004de0b99 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Tests\Export\Aggregator\PersonAggregators; +use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; @@ -18,6 +19,7 @@ use Prophecy\PhpUnit\ProphecyTrait; /** * @internal + * * @coversNothing */ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest @@ -30,7 +32,7 @@ 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); @@ -56,23 +58,21 @@ 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'), $em->createQueryBuilder() ->select('count(activity.id)') - ->from('ChillActivityBundle:Activity', 'activity') + ->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/Export/LinkedToACP/AvgActivityDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityDurationTest.php index 2eb2176b0..5f9b635ee 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityDurationTest.php @@ -16,6 +16,7 @@ use Chill\MainBundle\Test\Export\AbstractExportTest; /** * @internal + * * @coversNothing */ final class AvgActivityDurationTest extends AbstractExportTest diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php index 59b4384cf..b15462912 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php @@ -16,6 +16,7 @@ use Chill\MainBundle\Test\Export\AbstractExportTest; /** * @internal + * * @coversNothing */ final class AvgActivityVisitDurationTest extends AbstractExportTest diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php index f03bbb956..46279ebd8 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php @@ -16,6 +16,7 @@ use Chill\MainBundle\Test\Export\AbstractExportTest; /** * @internal + * * @coversNothing */ final class CountActivityTest extends AbstractExportTest diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php index 24a5133b6..c4f91f52e 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php @@ -16,6 +16,7 @@ use Chill\MainBundle\Test\Export\AbstractExportTest; /** * @internal + * * @coversNothing */ final class SumActivityDurationTest extends AbstractExportTest diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php index 1940140ac..6bd7e9d19 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php @@ -16,6 +16,7 @@ use Chill\MainBundle\Test\Export\AbstractExportTest; /** * @internal + * * @coversNothing */ final class SumActivityVisitDurationTest extends AbstractExportTest diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php index 864ff55ba..61ae15604 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php @@ -16,6 +16,7 @@ use Chill\MainBundle\Test\Export\AbstractExportTest; /** * @internal + * * @coversNothing */ final class CountActivityTest extends AbstractExportTest diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php index afa3da111..f474b1fe6 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php @@ -17,6 +17,7 @@ use Prophecy\PhpUnit\ProphecyTrait; /** * @internal + * * @coversNothing */ final class ListActivityTest extends AbstractExportTest diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php index 44d327ffd..11fb84a9a 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php @@ -18,6 +18,7 @@ use Chill\MainBundle\Test\Export\AbstractExportTest; * Test the "sum" part of StatActivityDuration. * * @internal + * * @coversNothing */ final class StatActivityDurationTest extends AbstractExportTest diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php index 72b99375b..347d7466f 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php @@ -13,25 +13,26 @@ 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\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 +42,21 @@ 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' => new ArrayCollection([$a]), ]; } @@ -62,9 +65,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/ActivityReasonFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php index 26d5248ae..3d1f1fa2d 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,42 @@ final class ActivityReasonFilterTest extends AbstractFilterTest public function getFormData() { - if (null === self::$kernel) { - self::bootKernel(); - } + self::bootKernel(); - $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))]; + yield ['reasons' => new ArrayCollection(array_splice($reasons, ($i + 1) * -1))]; } - return $r; + self::ensureKernelShutdown(); } - 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..5f94b4579 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,23 @@ final class ActivityTypeFilterTest extends AbstractFilterTest foreach ($array as $a) { $data[] = [ - 'types' => $a, + 'types' => new ArrayCollection([$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/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..a2ea7c158 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 @@ -35,7 +39,7 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest $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/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/Export/Filter/ACPFilters/UserScopeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserScopeFilterTest.php similarity index 80% rename from src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserScopeFilterTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserScopeFilterTest.php index 5742662b2..858931327 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserScopeFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserScopeFilterTest.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\UserScopeFilter; +use Chill\ActivityBundle\Export\Filter\UserScopeFilter; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Doctrine\ORM\EntityManagerInterface; /** * @internal + * * @coversNothing */ final class UserScopeFilterTest extends AbstractFilterTest @@ -29,7 +30,7 @@ final class UserScopeFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.activity.export.userscope_filter'); + $this->filter = self::$container->get(UserScopeFilter::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/Form/ActivityTypeTest.php b/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php index df0a602a4..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(); @@ -188,9 +182,7 @@ final class ActivityTypeTest extends KernelTestCase // map all the values in an array $values = array_map( - static function ($choice) { - return $choice->value; - }, + static fn ($choice) => $choice->value, $view['activity']['durationTime']->vars['choices'] ); diff --git a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php index 3c9777051..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() @@ -79,15 +83,13 @@ final class TranslatableActivityReasonTest extends TypeTestCase $request = $prophet->prophesize(); $translator = $prophet->prophesize(); - $request->willExtend('Symfony\Component\HttpFoundation\Request'); + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn($fallbackLocale); - $requestStack->willExtend('Symfony\Component\HttpFoundation\RequestStack'); - $requestStack->getCurrentRequest()->will(static function () use ($request) { - return $request; - }); + $requestStack->willExtend(\Symfony\Component\HttpFoundation\RequestStack::class); + $requestStack->getCurrentRequest()->will(static fn () => $request); - $translator->willExtend('Symfony\Component\Translation\Translator'); + $translator->willExtend(\Symfony\Component\Translation\Translator::class); $translator->getFallbackLocales()->willReturn($locale); return new TranslatableStringHelper( diff --git a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityTypeTest.php b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityTypeTest.php index 770b88cf4..80779d909 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, @@ -89,11 +83,9 @@ final class TranslatableActivityTypeTest extends KernelTestCase } /** - * @param mixed $active - * * @return \Chill\ActivityBundle\Entity\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..88eb3e7f8 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php @@ -0,0 +1,326 @@ +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 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() + ) { + 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'); + } + + 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 new file mode 100644 index 000000000..c32714641 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php @@ -0,0 +1,125 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + $this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class); + $this->authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class); + $this->security = self::$container->get(Security::class); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, array $reachableScopes, bool $_unused, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $authorizationHelper->getReachableScopes(ActivityVoter::SEE, Argument::any()) + ->willReturn($reachableScopes); + + $repository = new ActivityDocumentACLAwareRepository( + $this->entityManager, + $this->centerResolverManager, + $authorizationHelper->reveal(), + $this->security + ); + + $query = $repository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content); + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types); + + self::assertIsInt($nb); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, array $_unused, bool $canSeePeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $security = $this->prophesize(Security::class); + $security->isGranted(ActivityVoter::SEE, Argument::type(AccompanyingPeriod::class)) + ->willReturn($canSeePeriod); + + $repository = new ActivityDocumentACLAwareRepository( + $this->entityManager, + $this->centerResolverManager, + $this->authorizationHelperForCurrentUser, + $security->reveal() + ); + + $query = $repository->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types); + + self::assertIsInt($nb); + } + + public function provideDataForPerson(): iterable + { + $this->setUp(); + + 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'); + } + + 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']; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php b/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php index a9b9e2ec7..b907363a3 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 */ @@ -156,11 +157,11 @@ final class ActivityVoterTest extends KernelTestCase * * @return \Symfony\Component\Security\Core\Authentication\Token\TokenInterface */ - protected function prepareToken(?User $user = null) + protected function prepareToken(User $user = null) { $token = $this->prophet->prophesize(); $token - ->willImplement('\Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + ->willImplement('\\'.\Symfony\Component\Security\Core\Authentication\Token\TokenInterface::class); if (null === $user) { $token->getUser()->willReturn(null); 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 dad597676..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,7 +139,8 @@ class TimelineActivityProvider implements TimelineProviderInterface // loop on reachable scopes foreach ($reachableScopes as $scope) { - if (in_array($scope->getId(), $scopes_ids, true)) { + /* @phpstan-ignore-next-line */ + if (\in_array($scope->getId(), $scopes_ids, true)) { continue; } $scopes_ids[] = '?'; @@ -169,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.yaml b/src/Bundle/ChillActivityBundle/config/services.yaml index 23f00ed4e..18be76ec9 100644 --- a/src/Bundle/ChillActivityBundle/config/services.yaml +++ b/src/Bundle/ChillActivityBundle/config/services.yaml @@ -34,6 +34,10 @@ services: resource: '../Validator/Constraints/' Chill\ActivityBundle\Service\DocGenerator\: - autowire: true - autoconfigure: true resource: '../Service/DocGenerator/' + + Chill\ActivityBundle\Service\EntityInfo\: + resource: '../Service/EntityInfo/' + + Chill\ActivityBundle\Service\GenericDoc\: + resource: '../Service/GenericDoc/' diff --git a/src/Bundle/ChillActivityBundle/config/services/export.yaml b/src/Bundle/ChillActivityBundle/config/services/export.yaml index 09817d80e..09a5227eb 100644 --- a/src/Bundle/ChillActivityBundle/config/services/export.yaml +++ b/src/Bundle/ChillActivityBundle/config/services/export.yaml @@ -53,8 +53,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,52 +73,43 @@ 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\UserScopeFilter: tags: - { name: chill.export_filter, alias: 'activity_userscope_filter' } @@ -135,6 +125,10 @@ 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' } + ## Aggregators Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator: tags: @@ -144,27 +138,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 +173,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\JobScopeAggregator: + tags: + - { name: chill.export_aggregator, alias: activity_creator_job_aggregator } + Chill\ActivityBundle\Export\Aggregator\ActivityUsersAggregator: tags: - { name: chill.export_aggregator, alias: activity_users_aggregator } 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..7c02e29c6 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/translations/messages+intl-icu.fr.yml @@ -0,0 +1,11 @@ +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} + person_between_dates: + 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 042fccc69..123a0d075 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -83,12 +83,20 @@ 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%"' @@ -247,11 +255,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 @@ -279,8 +282,6 @@ Filter activity by creator: Filtrer les échanges par créateur de l'échange '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 @@ -298,10 +299,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 @@ -312,7 +309,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 @@ -359,12 +355,29 @@ 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: + by_users_scope: Filter by users scope: Filtrer les échanges par services 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: Filtre 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: + 'acp_containing_at_least_one_%activitytypes%': 'Parcours filtrés: uniquement ceux qui contiennent au moins un échange d''un des types suivants: %activitytypes%' + 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 personnes 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: uniquement %scopes%" + aggregator: activity: by_sent_received: @@ -372,3 +385,23 @@ export: 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 service du créateur de l'échange + Calc date: Date de calcul du service du créateur de l'échange + +generic_doc: + filter: + keys: + accompanying_period_activity_document: Document des échanges des parcours diff --git a/src/Bundle/ChillAsideActivityBundle/src/ChillAsideActivityBundle.php b/src/Bundle/ChillAsideActivityBundle/src/ChillAsideActivityBundle.php index 6917517b7..b0951e502 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/ChillAsideActivityBundle.php +++ b/src/Bundle/ChillAsideActivityBundle/src/ChillAsideActivityBundle.php @@ -13,6 +13,4 @@ namespace Chill\AsideActivityBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; -class ChillAsideActivityBundle extends Bundle -{ -} +class ChillAsideActivityBundle extends Bundle {} 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..c29a5a6dc 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php @@ -16,26 +16,21 @@ 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) - { - $this->categoryRepository = $categoryRepository; - } + public function __construct(private readonly AsideActivityCategoryRepository $categoryRepository) {} public function createEntity(string $action, Request $request): object { $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); @@ -52,7 +47,7 @@ final class AsideActivityController extends CRUDController return $asideActivity; } - protected function buildQueryEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null) + protected function buildQueryEntities(string $action, Request $request, FilterOrderHelper $filterOrder = null) { $qb = parent::buildQueryEntities($action, $request); diff --git a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php index 6f6a11390..12c82be00 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php @@ -14,22 +14,13 @@ 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) - { - $this->userRepository = $userRepository; - } + public function __construct(private readonly UserRepository $userRepository) {} public function getDependencies(): array { @@ -47,15 +38,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 d01998358..241a545a8 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php @@ -14,15 +14,13 @@ 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() { $treeBuilder = new TreeBuilder('chill_aside_activity'); - $treeBuilder->getRootNode('chill_aside_activity') + $treeBuilder->getRootNode() ->children() ->arrayNode('form') ->canBeEnabled() @@ -132,9 +130,7 @@ class Configuration implements ConfigurationInterface ->info('The number of seconds of this duration. Must be an integer.') ->cannotBeEmpty() ->validate() - ->ifTrue(static function ($data) { - return !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 598988cfb..2bb8a985d 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php @@ -14,19 +14,22 @@ 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; @@ -34,10 +37,11 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface /** * @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; @@ -45,40 +49,45 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface /** * @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; + 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 ?\Chill\AsideActivityBundle\Entity\AsideActivityCategory $type = null; /** * @ORM\Column(type="datetime", nullable=true) */ - private $updatedAt; + private ?\DateTimeInterface $updatedAt = null; /** * @ORM\ManyToOne(targetEntity=User::class) @@ -90,7 +99,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface 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 32418e3c3..4271ae118 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php @@ -20,15 +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 { @@ -51,6 +46,11 @@ class ByActivityTypeAggregator implements AggregatorInterface // No form needed } + public function getFormDefaultData(): array + { + return []; + } + public function getLabels($key, array $values, $data) { return function ($value): string { 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..b5ca1022b --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php @@ -0,0 +1,73 @@ +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 d2fe7c2f2..c3883b18a 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php @@ -12,25 +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,23 +36,40 @@ 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) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { - // nothing to add in the form + return []; } public function getLabels($key, array $values, $data) @@ -79,11 +93,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 6c06ee756..a99d2b75f 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php @@ -12,25 +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,23 +36,39 @@ 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) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { - // nothing to add in the form + return []; } public function getLabels($key, array $values, $data) @@ -79,11 +92,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 f3db629cb..70922b6ae 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php @@ -18,21 +18,17 @@ 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(private readonly AsideActivityRepository $repository) {} - public function __construct( - AsideActivityRepository $repository - ) { - $this->repository = $repository; - } + public function buildForm(FormBuilderInterface $builder) {} - public function buildForm(FormBuilderInterface $builder) + public function getFormDefaultData(): array { + return []; } public function getAllowedFormattersTypes(): array @@ -53,7 +49,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 87aad1659..6d1eed5fe 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php @@ -18,21 +18,17 @@ 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(private readonly AsideActivityRepository $repository) {} - public function __construct( - AsideActivityRepository $repository - ) { - $this->repository = $repository; - } + public function buildForm(FormBuilderInterface $builder) {} - public function buildForm(FormBuilderInterface $builder) + public function getFormDefaultData(): array { + return []; } public function getAllowedFormattersTypes(): array @@ -53,15 +49,13 @@ 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); $labels['_header'] = $this->getTitle(); - return static function ($value) use ($labels) { - return $labels[$value]; - }; + return static fn ($value) => $labels[$value]; } public function getQueryKeys($data): array diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php index aee168174..33155c62f 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php @@ -23,55 +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 - ) { - $this->em = $em; - $this->dateTimeHelper = $dateTimeHelper; - $this->userHelper = $userHelper; - $this->scopeRepository = $scopeRepository; - $this->centerRepository = $centerRepository; - $this->asideActivityCategoryRepository = $asideActivityCategoryRepository; - $this->categoryRender = $categoryRender; - $this->translatableStringHelper = $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 + ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { + return []; } public function getAllowedFormattersTypes() @@ -91,86 +68,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) @@ -187,6 +156,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface 'date', 'duration', 'note', + 'location', ]; } @@ -209,7 +179,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') @@ -217,11 +190,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 af17a2591..0fd318902 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php @@ -18,21 +18,17 @@ 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(private readonly AsideActivityRepository $repository) {} - public function __construct( - AsideActivityRepository $repository - ) { - $this->repository = $repository; - } + public function buildForm(FormBuilderInterface $builder) {} - public function buildForm(FormBuilderInterface $builder) + public function getFormDefaultData(): array { + return []; } public function getAllowedFormattersTypes(): array @@ -53,7 +49,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 3ad8e0e93..b78640d49 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php @@ -23,21 +23,11 @@ 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 - ) { - $this->categoryRender = $categoryRender; - $this->asideActivityTypeRepository = $asideActivityTypeRepository; - $this->translatableStringHelper = $translatableStringHelper; - } + private readonly CategoryRender $categoryRender, + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly AsideActivityCategoryRepository $asideActivityTypeRepository + ) {} public function addRole(): ?string { @@ -77,6 +67,11 @@ class ByActivityTypeFilter implements FilterInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $types = array_map( diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php index 7a1b6f4dc..8f3c9c305 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php @@ -26,17 +26,7 @@ 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 { @@ -72,11 +62,9 @@ class ByDateFilter implements FilterInterface $builder ->add('date_from', PickRollingDateType::class, [ 'label' => 'export.filter.Aside activities after this date', - 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), ]) ->add('date_to', PickRollingDateType::class, [ 'label' => 'export.filter.Aside activities before this date', - 'data' => new RollingDate(RollingDate::T_TODAY), ]); $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { @@ -94,14 +82,14 @@ class ByDateFilter implements FilterInterface if (null === $date_from) { $form->get('date_from')->addError(new FormError( $this->translator->trans('This field ' - . 'should not be empty') + .'should not be empty') )); } if (null === $date_to) { $form->get('date_to')->addError(new FormError( $this->translator->trans('This field ' - . 'should not be empty') + .'should not be empty') )); } @@ -112,14 +100,19 @@ class ByDateFilter implements FilterInterface ) { $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') + .'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)]; + } + public function describeAction($data, $format = 'string'): array { return ['export.filter.Filtered by aside activities between %dateFrom% and %dateTo%', [ 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..0be041dc9 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php @@ -0,0 +1,81 @@ +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 + { + $locations = $data['locations']->map(fn (Location $l): string => $l->getName()); + + 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 795c813cd..8dd1a8eac 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php @@ -20,12 +20,7 @@ use Symfony\Component\Form\FormBuilderInterface; class ByUserFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) - { - $this->userRender = $userRender; - } + public function __construct(private readonly UserRender $userRender) {} public function addRole(): ?string { @@ -54,6 +49,11 @@ class ByUserFilter implements FilterInterface ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $users = []; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php index 86194b123..3d5bb6530 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php @@ -13,6 +13,7 @@ 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\Templating\TranslatableStringHelperInterface; @@ -22,12 +23,11 @@ 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 + ) {} public function addRole(): ?string { @@ -36,34 +36,46 @@ 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, - ]); + $builder + ->add('jobs', EntityType::class, [ + 'class' => UserJob::class, + '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( @@ -74,8 +86,15 @@ class ByUserJobFilter implements FilterInterface ]]; } - 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 4342e11eb..846814785 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php @@ -14,6 +14,7 @@ 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; @@ -23,17 +24,12 @@ 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 - ) { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; - } + private readonly ScopeRepositoryInterface $scopeRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) {} public function addRole(): ?string { @@ -42,35 +38,47 @@ 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, - ]); + $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( @@ -81,8 +89,15 @@ class ByUserScopeFilter implements FilterInterface ]]; } - 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..3a69be137 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php @@ -22,13 +22,7 @@ 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 9a95a8d09..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,27 +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() === null ? - DateTime::createFromFormat('U', '300') : - $formEvent->getData(); + $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..23923fb6c 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php @@ -20,13 +20,7 @@ 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..43a37e068 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php @@ -14,14 +14,9 @@ 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) - { - $this->security = $security; - } + public function __construct(private 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..a32d52303 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php @@ -21,19 +21,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class SectionMenuBuilder implements LocalMenuBuilderInterface { - public AuthorizationCheckerInterface $authorizationChecker; + public function __construct(protected TranslatorInterface $translator, public AuthorizationCheckerInterface $authorizationChecker) {} - protected TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator, 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..533e567b4 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) { @@ -49,7 +49,7 @@ class AsideActivityCategoryRepository implements ObjectRepository * * @return AsideActivityCategory[] */ - public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } 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..2598c4e01 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,15 +26,7 @@ final class CategoryRender implements ChillEntityRenderInterface public const SEPERATOR_KEY = 'default.separator'; - private EngineInterface $engine; - - private TranslatableStringHelper $translatableStringHelper; - - public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine) - { - $this->translatableStringHelper = $translatableStringHelper; - $this->engine = $engine; - } + public function __construct(private TranslatableStringHelper $translatableStringHelper, private \Twig\Environment $engine) {} public function buildParents(AsideActivityCategory $asideActivityCategory) { 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 cc428390c..0a324d2cd 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: @@ -176,11 +177,13 @@ export: agent_id: Utilisateur creator_id: Créateur main_scope: Service principal de l'utilisateur - main_center: Centre principal de l'utilisteur + main_center: Centre principal de l'utilisateur aside_activity_type: Catégorie d'activité annexe date: Date 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 @@ -197,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 380e641a6..ce31dcaaf 100644 --- a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php +++ b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php @@ -13,15 +13,15 @@ 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; - public $result; + public float $result; public $type; } 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 badccf3b3..38d4d82f5 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php @@ -23,39 +23,20 @@ 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) {} /** * Route( * "{_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 +48,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(), @@ -116,7 +97,7 @@ abstract class AbstractElementController extends AbstractController $indexPage = 'chill_budget_elements_household_index'; } - $entity = null !== $element->getPerson() ? $element->getPerson() : $element->getHousehold(); + $entity = $element->getPerson() ?? $element->getHousehold(); $form = $this->createForm($this->getType(), $element); $form->add('submit', SubmitType::class); @@ -141,12 +122,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 +172,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..642b7cc61 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php @@ -20,7 +20,7 @@ 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; diff --git a/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php b/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php index 90b8e750a..0762f8252 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php @@ -20,7 +20,7 @@ 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; 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..26acbf8a5 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/ElementController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/ElementController.php @@ -12,46 +12,19 @@ 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 +33,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 +48,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 +57,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/DependencyInjection/Compiler/CalculatorCompilerPass.php b/src/Bundle/ChillBudgetBundle/DependencyInjection/Compiler/CalculatorCompilerPass.php index 64df79202..012c4eab5 100644 --- a/src/Bundle/ChillBudgetBundle/DependencyInjection/Compiler/CalculatorCompilerPass.php +++ b/src/Bundle/ChillBudgetBundle/DependencyInjection/Compiler/CalculatorCompilerPass.php @@ -19,7 +19,7 @@ class CalculatorCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - $manager = $container->getDefinition('Chill\BudgetBundle\Calculator\CalculatorManager'); + $manager = $container->getDefinition(\Chill\BudgetBundle\Calculator\CalculatorManager::class); foreach ($container->findTaggedServiceIds('chill_budget.calculator') as $id => $tags) { foreach ($tags as $tag) { diff --git a/src/Bundle/ChillBudgetBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillBudgetBundle/DependencyInjection/Configuration.php index 8856df22b..ff9931f2d 100644 --- a/src/Bundle/ChillBudgetBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillBudgetBundle/DependencyInjection/Configuration.php @@ -19,7 +19,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder('chill_budget'); - $rootNode = $treeBuilder->getRootNode('chill_budget'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() diff --git a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php index f12ba3d72..52a52c99f 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" * ) @@ -41,16 +41,17 @@ abstract class AbstractElement /** * @ORM\Column(name="comment", type="text", nullable=true) */ - private ?string $comment; + private ?string $comment = null; /** * @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; + 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; } @@ -130,17 +132,17 @@ abstract class AbstractElement return $this; } - public function setComment(?string $comment = null): self + public function setComment(string $comment = null): self { $this->comment = $comment; 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 3356057cf..2d219b62a 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php @@ -27,21 +27,7 @@ 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) { @@ -52,9 +38,7 @@ class ChargeType extends AbstractType 'label' => 'Charge type', 'required' => true, 'placeholder' => $this->translator->trans('admin.form.Choose the type of charge'), - 'choice_label' => function (ChargeKind $resource) { - return $this->translatableStringHelper->localize($resource->getName()); - }, + 'choice_label' => fn (ChargeKind $resource) => $this->translatableStringHelper->localize($resource->getName()), 'attr' => ['class' => 'select2'], ]) ->add('amount', MoneyType::class) diff --git a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php index fd859217a..ba93f1080 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php @@ -26,21 +26,7 @@ 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) { @@ -51,9 +37,7 @@ class ResourceType extends AbstractType 'label' => 'Resource type', 'required' => true, 'placeholder' => $this->translator->trans('admin.form.Choose the type of resource'), - 'choice_label' => function (ResourceKind $resource) { - return $this->translatableStringHelper->localize($resource->getName()); - }, + 'choice_label' => fn (ResourceKind $resource) => $this->translatableStringHelper->localize($resource->getName()), 'attr' => ['class' => 'select2'], ]) ->add('amount', MoneyType::class) diff --git a/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php index 894230ad9..b8b4b617c 100644 --- a/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php @@ -15,14 +15,9 @@ 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) - { - $this->security = $security; - } + public function __construct(private 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..94583b439 100644 --- a/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php +++ b/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php @@ -20,17 +20,7 @@ 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..25bd6a218 100644 --- a/src/Bundle/ChillBudgetBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillBudgetBundle/Menu/PersonMenuBuilder.php @@ -20,17 +20,7 @@ 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..577be07db 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) { @@ -66,7 +66,7 @@ final class ChargeKindRepository implements ChargeKindRepositoryInterface * * @return array */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php index f92851eba..7a930bea8 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php @@ -34,12 +34,9 @@ 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; + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array; public function findOneBy(array $criteria): ?ChargeKind; 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..e10913195 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]); } /** @@ -71,7 +71,7 @@ final class ResourceKindRepository implements ResourceKindRepositoryInterface * * @return list */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php index dac46ff35..d639d54ee 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php @@ -34,12 +34,9 @@ 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; + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array; public function findOneBy(array $criteria): ?ResourceKind; 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 70e96d96c..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) %} + - - - - + + + + @@ -16,8 +17,14 @@ @@ -32,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 %}
      @@ -63,7 +70,7 @@
      {{ 'Budget element type'|trans }}{{ 'Amount'|trans }}{{ 'Validity period'|trans }} {{ 'Budget element type'|trans }}{{ 'Amount'|trans }}{{ 'Validity period'|trans }} 
      {% if f.isResource %} {{ f.resource.name|localize_translatable_string }} + {% if f.resource.getKind is same as 'other' %} + : {{ f.getComment }} + {% endif %} {% else %} {{ f.charge.name|localize_translatable_string }} + {% if f.charge.getKind is same as 'other' %} + : {{ f.getComment }} + {% endif %} {% endif %} {{ f.amount|format_currency('EUR') }}
      {% endmacro %} -{% macro table_results(actualCharges, actualResources) %} +{% macro table_results(actualCharges, actualResources, results) %} {% set totalCharges = 0 %} {% for c in actualCharges %} @@ -91,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 ad2a014ed..a57f7fb29 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,25 +34,7 @@ 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 { @@ -66,10 +45,8 @@ final class SummaryBudget implements SummaryBudgetInterface ]; } - $personIds = $household->getCurrentPersons()->map(static function (Person $p) { - return $p->getId(); - }); - $ids = implode(', ', array_fill(0, count($personIds), '?')); + $personIds = $household->getCurrentPersons()->map(static fn (Person $p) => $p->getId()); + $ids = implode(', ', array_fill(0, \count($personIds), '?')); $parameters = [...$personIds, $household->getId()]; @@ -127,18 +104,14 @@ final class SummaryBudget implements SummaryBudgetInterface { $keys = array_map(static fn (ChargeKind $kind) => $kind->getKind(), $this->chargeKindRepository->findAll()); - return array_combine($keys, array_map(function ($kind) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->findOneByKind($kind)->getName()), 'comment' => '']; - }, $keys)); + return array_combine($keys, array_map(fn ($kind) => ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->findOneByKind($kind)->getName()), 'comment' => ''], $keys)); } private function getEmptyResourceArray(): array { $keys = array_map(static fn (ResourceKind $kind) => $kind->getKind(), $this->resourceKindRepository->findAll()); - return array_combine($keys, array_map(function ($kind) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->findOneByKind($kind)->getName()), 'comment' => '']; - }, $keys)); + return array_combine($keys, array_map(fn ($kind) => ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->findOneByKind($kind)->getName()), 'comment' => ''], $keys)); } private function rowToArray(array $rows, string $kind): array @@ -151,7 +124,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'], @@ -167,7 +140,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()] = [ @@ -180,7 +153,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..26871b0f4 100644 --- a/src/Bundle/ChillBudgetBundle/Templating/BudgetElementTypeRender.php +++ b/src/Bundle/ChillBudgetBundle/Templating/BudgetElementTypeRender.php @@ -15,22 +15,13 @@ 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) - { - $this->translatableStringHelper = $translatableStringHelper; - $this->engine = $engine; - } + public function __construct(private TranslatableStringHelperInterface $translatableStringHelper, private \Twig\Environment $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 7fcda6b11..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 @@ -50,9 +47,7 @@ final class SummaryBudgetTest extends TestCase ], ]); $queryCharges->setParameters(Argument::type('array')) - ->will(static function ($args, $query) { - return $query; - }); + ->will(static fn ($args, $query) => $query); $queryResources = $this->prophesize(AbstractQuery::class); $queryResources->getResult()->willReturn([ @@ -63,22 +58,20 @@ final class SummaryBudgetTest extends TestCase ], ]); $queryResources->setParameters(Argument::type('array')) - ->will(static function ($args, $query) { - return $query; - }); + ->will(static fn ($args, $query) => $query); $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); @@ -100,23 +93,21 @@ final class SummaryBudgetTest extends TestCase $resourceRepository->findOneByKind('misc')->willReturn($misc); $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); - $translatableStringHelper->localize(Argument::type('array'))->will(static function ($arg) { - return $arg[0]['fr']; - }); + $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/ChillBudgetBundle/migrations/Version20221207105407.php b/src/Bundle/ChillBudgetBundle/migrations/Version20221207105407.php index cfe3a0089..1d2707120 100644 --- a/src/Bundle/ChillBudgetBundle/migrations/Version20221207105407.php +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20221207105407.php @@ -32,7 +32,7 @@ final class Version20221207105407 extends AbstractMigration implements Container return 'Use new budget admin entities'; } - public function setContainer(?ContainerInterface $container = null) + public function setContainer(ContainerInterface $container = null) { $this->container = $container; } 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 d0fdd0a02..4964f0064 100644 --- a/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php +++ b/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php @@ -18,11 +18,11 @@ declare(strict_types=1); 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 DateInterval; -use DateTimeImmutable; +use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; @@ -30,120 +30,130 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class MapAndSubscribeUserCalendarCommand extends Command +final class MapAndSubscribeUserCalendarCommand extends Command { - private EntityManagerInterface $em; - - private EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator; - - private LoggerInterface $logger; - - private MapCalendarToUser $mapCalendarToUser; - - private MSGraphUserRepository $userRepository; - public function __construct( - EntityManagerInterface $em, - EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator, - LoggerInterface $logger, - MapCalendarToUser $mapCalendarToUser, - MSGraphUserRepository $userRepository + private readonly EntityManagerInterface $em, + private readonly EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator, + private readonly LoggerInterface $logger, + private readonly MapCalendarToUser $mapCalendarToUser, + private readonly UserRepositoryInterface $userRepository, + private readonly MSUserAbsenceSync $userAbsenceSync, ) { parent::__construct('chill:calendar:msgraph-user-map-subscribe'); - - $this->em = $em; - $this->eventsOnUserSubscriptionCreator = $eventsOnUserSubscriptionCreator; - $this->logger = $logger; - $this->mapCalendarToUser = $mapCalendarToUser; - $this->userRepository = $userRepository; } public function execute(InputInterface $input, OutputInterface $output): int { - $this->logger->info(__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'))); - $total = $this->userRepository->countByMostOldSubscriptionOrWithoutSubscriptionOrData($interval); + /** @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'))); + $users = $this->userRepository->findAllAsArray('fr'); $created = 0; $renewed = 0; - $this->logger->info(__CLASS__ . ' the number of user to get - renew', [ - 'total' => $total, - 'expiration' => $expiration->format(DateTimeImmutable::ATOM), + $this->logger->info(self::class.' start user to get - renew', [ + 'expiration' => $expiration->format(\DateTimeImmutable::ATOM), ]); - while ($offset < $total) { - $users = $this->userRepository->findByMostOldSubscriptionOrWithoutSubscriptionOrData( - $interval, - $limit, - $offset - ); + foreach ($users as $u) { + ++$offset; - foreach ($users as $user) { - if (!$this->mapCalendarToUser->hasUserId($user)) { - $this->mapCalendarToUser->writeMetadata($user); - } - - if ($this->mapCalendarToUser->hasUserId($user)) { - // 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(__CLASS__ . ' renew a subscription for', [ - 'userId' => $user->getId(), - 'username' => $user->getUsernameCanonical(), - ]); - - ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs] - = $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration); - $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret); - - if (0 !== $expirationTs) { - ++$renewed; - } else { - $this->logger->warning(__CLASS__ . ' could not renew subscription for a user', [ - 'userId' => $user->getId(), - 'username' => $user->getUsernameCanonical(), - ]); - } - } - - if (!$this->mapCalendarToUser->hasActiveSubscription($user)) { - $this->logger->debug(__CLASS__ . ' create a subscription for', [ - 'userId' => $user->getId(), - 'username' => $user->getUsernameCanonical(), - ]); - - ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs] - = $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration); - $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret); - - if (0 !== $expirationTs) { - ++$created; - } else { - $this->logger->warning(__CLASS__ . ' could not create subscription for a user', [ - 'userId' => $user->getId(), - 'username' => $user->getUsernameCanonical(), - ]); - } - } - } - - ++$offset; + if (false === $u['enabled']) { + continue; } - $this->em->flush(); - $this->em->clear(); + $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']); + continue; + } + + if (!$this->mapCalendarToUser->hasUserId($user)) { + $user = $this->mapCalendarToUser->writeMetadata($user); + + // 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())); + + continue; + } + } + + // sync user absence + 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; + } + + // 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', [ + 'userId' => $user->getId(), + 'username' => $user->getUsernameCanonical(), + ]); + + ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs] + = $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration); + $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret); + + if (0 !== $expirationTs) { + ++$renewed; + } else { + $this->logger->warning(self::class.' could not renew subscription for a user', [ + 'userId' => $user->getId(), + 'username' => $user->getUsernameCanonical(), + ]); + } + } + + if (!$this->mapCalendarToUser->hasActiveSubscription($user)) { + $this->logger->debug(self::class.' create a subscription for', [ + 'userId' => $user->getId(), + 'username' => $user->getUsernameCanonical(), + ]); + + ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs] + = $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration); + $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret); + + if (0 !== $expirationTs) { + ++$created; + } else { + $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(); + } } - $this->logger->warning(__CLASS__ . ' process executed', [ + $this->em->flush(); + $this->em->clear(); + + $this->logger->warning(self::class.' process executed', [ 'created' => $created, 'renewed' => $renewed, ]); + $output->writeln('users synchronized'); + return 0; } @@ -152,7 +162,7 @@ class MapAndSubscribeUserCalendarCommand extends Command parent::configure(); $this - ->setDescription('MSGraph: collect user metadata and create subscription on events for users') + ->setDescription('MSGraph: collect user metadata and create subscription on events for users, and sync the user absence-presence') ->addOption( 'renew-before-end-interval', 'r', 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..1729c215b 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,12 +23,7 @@ use Symfony\Component\Routing\Annotation\Route; class CalendarAPIController extends ApiController { - private CalendarRepository $calendarRepository; - - public function __construct(CalendarRepository $calendarRepository) - { - $this->calendarRepository = $calendarRepository; - } + public function __construct(private readonly CalendarRepository $calendarRepository) {} /** * @Route("/api/1.0/calendar/calendar/by-user/{id}.{_format}", @@ -45,8 +39,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 +50,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..f4694614f 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,57 +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 - ) { - $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; - } + 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 + ) {} /** * Delete a calendar item. @@ -119,12 +79,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 +135,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 +145,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 +162,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 +324,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 +339,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 +389,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(); @@ -490,7 +450,7 @@ class CalendarController extends AbstractController 'entity' => $entity, 'user' => $user, 'activityData' => $activityData, - //'delete_form' => $deleteForm->createView(), + // 'delete_form' => $deleteForm->createView(), ]); } @@ -535,12 +495,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 +509,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..d0f5e623d 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,41 +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 - ) { - $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; - $this->engine = $engine; - $this->entityManager = $entityManager; - $this->formFactory = $formFactory; - $this->security = $security; - $this->urlGenerator = $urlGenerator; - } + private \Twig\Environment $engine, + private EntityManagerInterface $entityManager, + private FormFactoryInterface $formFactory, + private Security $security, + private UrlGeneratorInterface $urlGenerator, + ) {} /** * @Route("/{_locale}/calendar/calendar-doc/{id}/new", name="chill_calendar_calendardoc_new") @@ -91,7 +65,7 @@ class CalendarDocController break; default: - throw new UnexpectedValueException('Unsupported context'); + throw new \UnexpectedValueException('Unsupported context'); } $calendarDocDTO = new CalendarDoc\CalendarDocCreateDTO(); @@ -208,7 +182,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..459d8f6aa 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php @@ -15,22 +15,15 @@ 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) - { - $this->calendarRangeRepository = $calendarRangeRepository; - } + public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository) {} /** * @Route("/api/1.0/calendar/calendar-range-available/{id}.{_format}", @@ -40,15 +33,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 +51,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..784a6f6ce 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php @@ -31,22 +31,10 @@ 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) - { - $this->entityManager = $entityManager; - $this->messageBus = $messageBus; - $this->security = $security; - } + public function __construct(private readonly EntityManagerInterface $entityManager, private readonly MessageBusInterface $messageBus, private readonly Security $security) {} /** * Give an answer to a calendar invite. @@ -69,7 +57,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..75b417e93 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php @@ -30,17 +30,7 @@ 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) {} /** * @Route("/{_locale}/connect/azure", name="chill_calendar_remote_connect_azure") diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php index 535dda2f5..e7d423abd 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php @@ -19,22 +19,15 @@ 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) - { - $this->messageBus = $messageBus; - } + public function __construct(private readonly MessageBusInterface $messageBus) {} /** * @Route("/public/incoming-hook/calendar/msgraph/events/{userId}", name="chill_calendar_remote_msgraph_incoming_webhook_events", @@ -49,8 +42,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..673912c0a 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php @@ -22,32 +22,19 @@ 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) - { - $this->paginatorFactory = $paginatorFactory; - $this->remoteCalendarConnector = $remoteCalendarConnector; - $this->serializer = $serializer; - } + public function __construct(private readonly PaginatorFactory $paginatorFactory, private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector, private readonly SerializerInterface $serializer) {} /** * @Route("api/1.0/calendar/proxy/calendar/by-user/{id}/events") @@ -58,8 +45,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 +56,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 +85,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..f222823bf 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,13 +28,7 @@ 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 +42,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 +62,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 +73,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 +91,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/DependencyInjection/Configuration.php b/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php index 127b69047..a3e4ae391 100644 --- a/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php @@ -24,7 +24,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder('chill_calendar'); - $rootNode = $treeBuilder->getRootNode('chill_calendar'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() diff --git a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php index d7f38adf0..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,13 +541,12 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente /** * @return ReadableCollection<(int|string), User> + * * @Serializer\Groups({"calendar:read", "read"}) */ public function getUsers(): ReadableCollection { - return $this->getInvites()->map(static function (Invite $i) { - return $i->getUser(); - }); + return $this->getInvites()->map(static fn (Invite $i) => $i->getUser()); } public function hasCalendarRange(): bool @@ -561,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; @@ -601,9 +631,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente } $invite = $this->invites - ->filter(static function (Invite $invite) use ($user) { - return $invite->getUser() === $user; - }) + ->filter(static fn (Invite $invite) => $invite->getUser() === $user) ->first(); $this->removeInvite($invite); @@ -657,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(); @@ -715,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(); @@ -730,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 14cfad98b..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; + 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..82c76eea4 100644 --- a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php +++ b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php @@ -15,16 +15,9 @@ 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) - { - $this->requestStack = $requestStack; - } + public function __construct(private readonly RequestStack $requestStack) {} public function postPersist(Activity $activity, LifecycleEventArgs $event): void { @@ -38,7 +31,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 new file mode 100644 index 000000000..dd2c0b9c2 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Exception/UserAbsenceSyncException.php @@ -0,0 +1,20 @@ +userRepository = $userRepository; - $this->userRender = $userRender; - } + public function __construct(private UserRepository $userRepository, private UserRender $userRender) {} public function addRole(): ?string { @@ -41,7 +29,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'); } @@ -59,7 +47,12 @@ final class AgentAggregator implements AggregatorInterface // no form } - public function getLabels($key, array $values, $data): Closure + public function getFormDefaultData(): array + { + return []; + } + + 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 611cb6d79..7c84653d2 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php @@ -15,24 +15,12 @@ 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 +30,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'); } @@ -60,7 +48,12 @@ class CancelReasonAggregator implements AggregatorInterface // no form } - public function getLabels($key, array $values, $data): Closure + public function getFormDefaultData(): array + { + return []; + } + + 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 23292a5b0..76cbe5cd8 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php @@ -12,27 +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 - ) { - $this->jobRepository = $jobRepository; - $this->translatableStringHelper = $translatableStringHelper; - } + private UserJobRepository $jobRepository, + private TranslatableStringHelper $translatableStringHelper + ) {} public function addRole(): ?string { @@ -41,12 +36,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 @@ -54,12 +65,14 @@ final class JobAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { - // no form + return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { @@ -70,7 +83,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() @@ -80,11 +95,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 940000f47..6481f95b4 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php @@ -14,20 +14,12 @@ 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 +28,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'); @@ -53,7 +45,12 @@ final class LocationAggregator implements AggregatorInterface // no form } - public function getLabels($key, array $values, $data): Closure + public function getFormDefaultData(): array + { + return []; + } + + 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 6574e3934..be9406cfa 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php @@ -15,24 +15,12 @@ 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 +29,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'); } @@ -59,7 +47,12 @@ final class LocationTypeAggregator implements AggregatorInterface // no form } - public function getLabels($key, array $values, $data): Closure + public function getFormDefaultData(): array + { + return []; + } + + 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 7b2a5e898..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; @@ -41,7 +40,12 @@ class MonthYearAggregator implements AggregatorInterface // No form needed } - public function getLabels($key, array $values, $data): Closure + public function getFormDefaultData(): array + { + return []; + } + + 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 3aff3e0d8..4998f6d1f 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php @@ -12,27 +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 - ) { - $this->scopeRepository = $scopeRepository; - $this->translatableStringHelper = $translatableStringHelper; - } + private ScopeRepository $scopeRepository, + private TranslatableStringHelper $translatableStringHelper + ) {} public function addRole(): ?string { @@ -41,12 +36,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 @@ -54,12 +65,14 @@ final class ScopeAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array { - // no form + return []; } - public function getLabels($key, array $values, $data): Closure + public function getLabels($key, array $values, $data): \Closure { return function ($value): string { if ('_header' === $value) { @@ -70,7 +83,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() @@ -80,11 +95,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 ad5910461..e9213d3cb 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php @@ -20,21 +20,13 @@ 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 { @@ -57,23 +49,23 @@ class UrgencyAggregator implements AggregatorInterface // no form } - public function getLabels($key, array $values, $data): Closure + public function getFormDefaultData(): array + { + return []; + } + + 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 f3bf79547..2e156d7a0 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,18 +25,18 @@ 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 []; + } + public function getAllowedFormattersTypes(): array { return [FormatterInterface::TYPE_TABULAR]; @@ -61,9 +61,7 @@ class CountCalendars implements ExportInterface, GroupedExportInterface $labels = array_combine($values, $values); $labels['_header'] = $this->getTitle(); - return static function ($value) use ($labels) { - return $labels[$value]; - }; + return static fn ($value) => $labels[$value]; } public function getQueryKeys($data): array @@ -91,13 +89,14 @@ class CountCalendars implements ExportInterface, GroupedExportInterface */ public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder { - $centers = array_map(static function ($el) { - return $el['center']; - }, $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $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 ddecba415..b69185a17 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php @@ -13,30 +13,29 @@ 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 []; + } + public function getAllowedFormattersTypes(): array { return [FormatterInterface::TYPE_TABULAR]; @@ -55,15 +54,13 @@ 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); $labels['_header'] = $this->getTitle(); - return static function ($value) use ($labels) { - return $labels[$value]; - }; + return static fn ($value) => $labels[$value]; } public function getQueryKeys($data): array @@ -90,8 +87,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 d99e73a2e..8ea23014c 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php @@ -13,30 +13,29 @@ 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 []; + } + public function getAllowedFormattersTypes(): array { return [FormatterInterface::TYPE_TABULAR]; @@ -55,15 +54,13 @@ 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); $labels['_header'] = $this->getTitle(); - return static function ($value) use ($labels) { - return $labels[$value]; - }; + return static fn ($value) => $labels[$value]; } public function getQueryKeys($data): array @@ -90,8 +87,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 18a4b0f4b..c16c148fc 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php @@ -22,12 +22,7 @@ use Symfony\Component\Form\FormBuilderInterface; class AgentFilter implements FilterInterface { - private UserRender $userRender; - - public function __construct(UserRender $userRender) - { - $this->userRender = $userRender; - } + public function __construct(private readonly UserRender $userRender) {} public function addRole(): ?string { @@ -58,14 +53,17 @@ class AgentFilter implements FilterInterface { $builder->add('accepted_agents', EntityType::class, [ 'class' => User::class, - 'choice_label' => function (User $u) { - return $this->userRender->renderString($u, []); - }, + 'choice_label' => fn (User $u) => $this->userRender->renderString($u, []), 'multiple' => true, 'expanded' => true, ]); } + public function getFormDefaultData(): array + { + return []; + } + public function describeAction($data, $format = 'string'): array { $users = []; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php index 59019ac03..90a004388 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php @@ -21,12 +21,7 @@ use Symfony\Component\Form\FormBuilderInterface; class BetweenDatesFilter implements FilterInterface { - private RollingDateConverterInterface $rollingDateConverter; - - public function __construct(RollingDateConverterInterface $rollingDateConverter) - { - $this->rollingDateConverter = $rollingDateConverter; - } + public function __construct(private readonly RollingDateConverterInterface $rollingDateConverter) {} public function addRole(): ?string { @@ -60,12 +55,13 @@ class BetweenDatesFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { $builder - ->add('date_from', PickRollingDateType::class, [ - 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), - ]) - ->add('date_to', PickRollingDateType::class, [ - 'data' => new RollingDate(RollingDate::T_TODAY), - ]); + ->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)]; } public function describeAction($data, $format = 'string'): array diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php index d6c38e163..63149509f 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,18 +28,13 @@ 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) - { - $this->translator = $translator; - } + public function __construct(private readonly TranslatorInterface $translator) {} public function addRole(): ?string { @@ -69,10 +63,14 @@ class CalendarRangeFilter implements FilterInterface 'multiple' => false, 'expanded' => true, 'empty_data' => self::DEFAULT_CHOICE, - 'data' => self::DEFAULT_CHOICE, ]); } + public function getFormDefaultData(): array + { + return ['hasCalendarRange' => self::DEFAULT_CHOICE]; + } + public function describeAction($data, $format = 'string'): array { $choice = ''; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php index 0f0f42adc..c122a298d 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php @@ -12,29 +12,22 @@ 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\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 - ) { - $this->translator = $translator; - $this->translatableStringHelper = $translatableStringHelper; - } + private TranslatableStringHelper $translatableStringHelper + ) {} public function addRole(): ?string { @@ -43,21 +36,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,16 +70,15 @@ class JobFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { - $builder->add('job', EntityType::class, [ - 'class' => UserJob::class, - 'choice_label' => function (UserJob $j) { - return $this->translatableStringHelper->localize( + $builder + ->add('job', EntityType::class, [ + 'class' => UserJob::class, + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize( $j->getLabel() - ); - }, - 'multiple' => true, - 'expanded' => true, - ]); + ), + 'multiple' => true, + 'expanded' => true, + ]); } public function describeAction($data, $format = 'string'): array @@ -89,13 +91,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 4d84543a3..93edc1b3a 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php @@ -13,28 +13,23 @@ 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\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 - ) { - $this->translator = $translator; - $this->translatableStringHelper = $translatableStringHelper; - } + protected TranslatorInterface $translator, + private readonly TranslatableStringHelper $translatableStringHelper + ) {} public function addRole(): ?string { @@ -43,43 +38,52 @@ 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' => function (Scope $s) { - return $this->translatableStringHelper->localize( + $builder + ->add('scope', EntityType::class, [ + 'class' => Scope::class, + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( $s->getName() - ); - }, - 'multiple' => true, - 'expanded' => true, - ]); + ), + 'multiple' => true, + 'expanded' => true, + ]); } - public function describeAction($data, $format = 'string') + public function describeAction($data, $format = 'string'): array { $scopes = []; @@ -89,13 +93,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..eec0b3f9f 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,33 +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 - ) { - $this->personsToIdDataTransformer = $personsToIdDataTransformer; - $this->idToUserDataTransformer = $idToUserDataTransformer; - $this->idToUsersDataTransformer = $idToUsersDataTransformer; - $this->idToLocationDataTransformer = $idToLocationDataTransformer; - $this->partiesToIdDataTransformer = $partiesToIdDataTransformer; - $this->calendarRangeDataTransformer = $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 + ) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -91,42 +71,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..6dd5bfa52 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -19,17 +19,7 @@ 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..e92a72bb7 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php @@ -19,17 +19,7 @@ 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..3a062f7b8 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php @@ -18,17 +18,7 @@ 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..8f62fdcdb 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,15 +29,7 @@ use Symfony\Component\Security\Core\Security; class CalendarEntityListener { - private MessageBusInterface $messageBus; - - private Security $security; - - public function __construct(MessageBusInterface $messageBus, Security $security) - { - $this->messageBus = $messageBus; - $this->security = $security; - } + public function __construct(private readonly MessageBusInterface $messageBus, private readonly 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..8b875bdcb 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,15 +29,7 @@ use Symfony\Component\Security\Core\Security; class CalendarRangeEntityListener { - private MessageBusInterface $messageBus; - - private Security $security; - - public function __construct(MessageBusInterface $messageBus, Security $security) - { - $this->messageBus = $messageBus; - $this->security = $security; - } + public function __construct(private readonly MessageBusInterface $messageBus, private readonly 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..7749d503c 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php @@ -31,15 +31,7 @@ use Symfony\Component\Messenger\Handler\MessageHandlerInterface; */ class CalendarRangeRemoveToRemoteHandler implements MessageHandlerInterface { - private RemoteCalendarConnectorInterface $remoteCalendarConnector; - - private UserRepository $userRepository; - - public function __construct(RemoteCalendarConnectorInterface $remoteCalendarConnector, UserRepository $userRepository) - { - $this->remoteCalendarConnector = $remoteCalendarConnector; - $this->userRepository = $userRepository; - } + public function __construct(private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector, private readonly 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..c9fd1b939 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php @@ -32,21 +32,7 @@ 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..73e8a0c37 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php @@ -31,18 +31,7 @@ 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) - { - $this->remoteCalendarConnector = $remoteCalendarConnector; - $this->calendarRangeRepository = $calendarRangeRepository; - $this->userRepository = $userRepository; - } + public function __construct(private readonly RemoteCalendarConnectorInterface $remoteCalendarConnector, private readonly CalendarRangeRepository $calendarRangeRepository, private readonly UserRepositoryInterface $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 9bcc0c434..6a1388d2e 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php @@ -37,33 +37,7 @@ 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) { @@ -89,14 +63,10 @@ class CalendarToRemoteHandler implements MessageHandlerInterface $newInvites = array_filter( array_map( - function ($id) { - return $this->inviteRepository->find($id); - }, + fn ($id) => $this->inviteRepository->find($id), $calendarMessage->getNewInvitesIds(), ), - static function (?Invite $invite) { - return null !== $invite; - } + static fn (?Invite $invite) => null !== $invite ); $this->calendarConnector->syncCalendar( diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php index e1df3ac3d..7ca5f2c12 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php @@ -31,18 +31,7 @@ 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) - { - $this->em = $em; - $this->inviteRepository = $inviteRepository; - $this->remoteCalendarConnector = $remoteCalendarConnector; - } + public function __construct(private readonly EntityManagerInterface $em, private readonly InviteRepository $inviteRepository, private readonly RemoteCalendarConnectorInterface $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 9c1e84511..7a67bee61 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php @@ -36,48 +36,14 @@ 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 { $user = $this->userRepository->find($changeNotificationMessage->getUserId()); if (null === $user) { - $this->logger->warning(__CLASS__ . ' notification concern non-existent user, skipping'); + $this->logger->warning(self::class.' notification concern non-existent user, skipping'); return; } @@ -86,7 +52,7 @@ class MSGraphChangeNotificationHandler implements MessageHandlerInterface $secret = $this->mapCalendarToUser->getSubscriptionSecret($user); if ($secret !== ($notification['clientState'] ?? -1)) { - $this->logger->warning(__CLASS__ . ' could not validate secret, skipping'); + $this->logger->warning(self::class.' could not validate secret, skipping'); continue; } @@ -101,7 +67,7 @@ class MSGraphChangeNotificationHandler implements MessageHandlerInterface $this->calendarSyncer->handleCalendarSync($calendar, $notification, $user); $this->em->flush(); } else { - $this->logger->info(__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 ec5977ad3..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,25 +45,22 @@ 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 ? $calendar->previousMainUser->getId() : null; $this->newInvitesIds = array_map(static fn (Invite $i) => $i->getId(), $calendar->newInvites); - $this->oldInvites = array_map(static function (Invite $i) { - return [ - 'inviteId' => $i->getId(), - 'userId' => $i->getUser()->getId(), - 'userEmail' => $i->getUser()->getEmail(), - 'userLabel' => $i->getUser()->getLabel(), - ]; - }, $calendar->oldInvites); + $this->oldInvites = array_map(static fn (Invite $i) => [ + 'inviteId' => $i->getId(), + 'userId' => $i->getUser()->getId(), + 'userEmail' => $i->getUser()->getEmail(), + 'userLabel' => $i->getUser()->getLabel(), + ], $calendar->oldInvites); } public function getAction(): string 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..15b8c6733 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php @@ -20,15 +20,7 @@ namespace Chill\CalendarBundle\Messenger\Message; class MSGraphChangeNotificationMessage { - private array $content; - - private int $userId; - - public function __construct(array $content, int $userId) - { - $this->content = $content; - $this->userId = $userId; - } + public function __construct(private readonly array $content, private readonly int $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..2535e23ca 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php @@ -24,15 +24,7 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface; class AddressConverter { - private AddressRender $addressRender; - - private TranslatableStringHelperInterface $translatableStringHelper; - - public function __construct(AddressRender $addressRender, TranslatableStringHelperInterface $translatableStringHelper) - { - $this->addressRender = $addressRender; - $this->translatableStringHelper = $translatableStringHelper; - } + public function __construct(private readonly AddressRender $addressRender, private readonly TranslatableStringHelperInterface $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..080140d86 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,21 @@ 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 +54,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 +77,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..f14683b9e 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php @@ -22,12 +22,7 @@ use Chill\MainBundle\Entity\Location; class LocationConverter { - private AddressConverter $addressConverter; - - public function __construct(AddressConverter $addressConverter) - { - $this->addressConverter = $addressConverter; - } + public function __construct(private readonly AddressConverter $addressConverter) {} public function locationToRemote(Location $location): array { diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php deleted file mode 100644 index c523a1e92..000000000 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php +++ /dev/null @@ -1,84 +0,0 @@ -'msgraph' ?? 'subscription_events_expiration' - OR (attributes->'msgraph' ?? 'subscription_events_expiration' AND (attributes->'msgraph'->>'subscription_events_expiration')::int < EXTRACT(EPOCH FROM (NOW() + :interval::interval))) - LIMIT :limit OFFSET :offset - ; - SQL; - - private EntityManagerInterface $entityManager; - - public function __construct(EntityManagerInterface $entityManager) - { - $this->entityManager = $entityManager; - } - - public function countByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval): int - { - $rsm = new ResultSetMapping(); - $rsm->addScalarResult('c', 'c'); - - $sql = strtr(self::MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH, [ - '{select}' => 'COUNT(u) AS c', - 'LIMIT :limit OFFSET :offset' => '', - ]); - - return $this->entityManager->createNativeQuery($sql, $rsm)->setParameters([ - 'interval' => $interval, - ])->getSingleScalarResult(); - } - - /** - * @return array|User[] - */ - public function findByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval, int $limit = 50, int $offset = 0): array - { - $rsm = new ResultSetMappingBuilder($this->entityManager); - $rsm->addRootEntityFromClassMetadata(User::class, 'u'); - - return $this->entityManager->createNativeQuery( - strtr(self::MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH, ['{select}' => $rsm->generateSelectClause()]), - $rsm - )->setParameters([ - 'interval' => $interval, - 'limit' => $limit, - 'offset' => $offset, - ])->getResult(); - } -} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php new file mode 100644 index 000000000..37e3e1996 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php @@ -0,0 +1,65 @@ +mapCalendarToUser->getUserId($user); + + if (null === $id) { + return null; + } + + try { + $automaticRepliesSettings = $this->machineHttpClient + ->request('GET', 'users/'.$id.'/mailboxSettings/automaticRepliesSetting') + ->toArray(true); + } catch (ClientExceptionInterface|DecodingExceptionInterface|RedirectionExceptionInterface|TransportExceptionInterface $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); + } + + 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() + && RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledEndDateTime']['dateTime']) > $this->clock->now(), + 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 new file mode 100644 index 000000000..f67562e27 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderInterface.php @@ -0,0 +1,22 @@ +absenceReader->isUserAbsent($user); + + if (null === $absence) { + return; + } + + if ($absence === $user->isAbsent()) { + // nothing to do + return; + } + + $this->logger->info('will change user absence', ['userId' => $user->getId()]); + + if ($absence) { + $this->logger->debug('make user absent', ['userId' => $user->getId()]); + $user->setAbsenceStart($this->clock->now()); + } else { + $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..ce490ce3f 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,13 +62,13 @@ 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); } - public function stream($responses, ?float $timeout = null): ResponseStreamInterface + public function stream($responses, float $timeout = null): ResponseStreamInterface { return $this->decoratedClient->stream($responses, $timeout); } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php index ac62d44ac..f2a0fc096 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php @@ -29,15 +29,7 @@ class MachineTokenStorage private ?AccessTokenInterface $accessToken = null; - private Azure $azure; - - private ChillRedis $chillRedis; - - public function __construct(Azure $azure, ChillRedis $chillRedis) - { - $this->azure = $azure; - $this->chillRedis = $chillRedis; - } + public function __construct(private readonly Azure $azure, private readonly 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..4b214e6d0 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php @@ -19,46 +19,33 @@ 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 +80,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 +111,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 +138,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 @@ -189,8 +176,8 @@ class MapCalendarToUser public function writeSubscriptionMetadata( User $user, int $expiration, - ?string $id = null, - ?string $secret = null + string $id = null, + string $secret = null ): void { $user->setAttributeByDomain(self::METADATA_KEY, self::EXPIRATION_SUBSCRIPTION_EVENT, $expiration); diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php index 9777bf4c0..d969a56b6 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,13 +57,13 @@ 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); } - public function stream($responses, ?float $timeout = null): ResponseStreamInterface + public function stream($responses, float $timeout = null): ResponseStreamInterface { return $this->decoratedClient->stream($responses, $timeout); } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php index 28f68e0e9..d8fff109b 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,17 +27,9 @@ 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) - { - $this->azure = $azure; - $this->session = $session; - } + public function __construct(private readonly Azure $azure, private readonly SessionInterface $session) {} public function getToken(): AccessToken { @@ -46,7 +37,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 ac1de552a..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,12 +99,10 @@ class RemoteEventConverter { $result = array_merge( [ - 'subject' => '[Chill] ' . + 'subject' => '[Chill] '. implode( ', ', - $calendar->getPersons()->map(function (Person $p) { - return $this->personRender->renderString($p, []); - })->toArray() + $calendar->getPersons()->map(fn (Person $p) => $this->personRender->renderString($p, []))->toArray() ), 'start' => [ 'dateTime' => $calendar->getStartDate()->setTimezone($this->remoteDateTimeZone) @@ -136,7 +115,7 @@ class RemoteEventConverter 'timeZone' => 'UTC', ], 'allowNewTimeProposals' => false, - 'transactionId' => 'calendar_' . $calendar->getId(), + 'transactionId' => 'calendar_'.$calendar->getId(), 'body' => [ 'contentType' => 'text', 'content' => $this->engine->render( @@ -161,9 +140,7 @@ class RemoteEventConverter { return [ 'attendees' => $calendar->getInvites()->map( - function (Invite $i) { - return $this->buildInviteToAttendee($i); - } + fn (Invite $i) => $this->buildInviteToAttendee($i) )->toArray(), ]; } @@ -171,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; } @@ -217,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( @@ -233,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; @@ -266,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 a9227282d..1dffe198c 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php @@ -24,30 +24,15 @@ 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 +44,7 @@ class CalendarRangeSyncer } $calendarRange->preventEnqueueChanges = true; - $this->logger->info(__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 +56,7 @@ class CalendarRangeSyncer $notification['resource'] )->toArray(); } catch (ClientExceptionInterface $clientException) { - $this->logger->warning(__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 +67,7 @@ class CalendarRangeSyncer $lastModified = RemoteEventConverter::convertStringDateWithTimezone($new['lastModifiedDateTime']); if ($calendarRange->getRemoteAttributes()['lastModifiedDateTime'] === $lastModified->getTimestamp()) { - $this->logger->info(__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 +89,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 c5a640f32..9b4daf626 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarSyncer.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarSyncer.php @@ -23,44 +23,21 @@ 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) - { - $this->logger = $logger; - $this->machineHttpClient = $machineHttpClient; - $this->userRepository = $userRepository; - } + public function __construct(private readonly LoggerInterface $logger, private readonly HttpClientInterface $machineHttpClient, private readonly UserRepositoryInterface $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 +56,7 @@ class CalendarSyncer $notification['resource'] )->toArray(); } catch (ClientExceptionInterface $clientException) { - $this->logger->warning(__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 +73,7 @@ class CalendarSyncer ); if ($calendar->getRemoteAttributes()['lastModifiedDateTime'] === $lastModified->getTimestamp()) { - $this->logger->info(__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 +114,7 @@ class CalendarSyncer } $email = $attendee['emailAddress']['address']; - $emails[] = strtolower($email); + $emails[] = strtolower((string) $email); $user = $this->userRepository->findOneByUsernameOrEmail($email); if (null === $user) { @@ -150,38 +127,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 d14ebaa02..768ad2a44 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,14 @@ 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; + 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) {} - 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 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 +54,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 +66,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 +103,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 +111,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 +129,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()), @@ -190,23 +141,17 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface ] )->toArray(); - $ids = array_map(static function ($item) { - return $item['id']; - }, $bareEvents['value']); + $ids = array_map(static fn ($item) => $item['id'], $bareEvents['value']); $existingIdsInRange = $this->calendarRangeRepository->findRemoteIdsPresent($ids); $existingIdsInCalendar = $this->calendarRepository->findRemoteIdsPresent($ids); return array_values( array_map( - function ($item) { - return $this->remoteEventConverter->convertToRemote($item); - }, + fn ($item) => $this->remoteEventConverter->convertToRemote($item), // filter all event to keep only the one not in range array_filter( $bareEvents['value'], - static function ($item) use ($existingIdsInRange, $existingIdsInCalendar) { - return ((!$existingIdsInRange[$item['id']]) ?? true) && ((!$existingIdsInCalendar[$item['id']]) ?? true); - } + static fn ($item) => ((!$existingIdsInRange[$item['id']]) ?? true) && ((!$existingIdsInCalendar[$item['id']]) ?? true) ) ) ); @@ -224,7 +169,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface } } - public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, ?CalendarRange $associatedCalendarRange = null): void + public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, CalendarRange $associatedCalendarRange = null): void { if ('' === $remoteId) { return; @@ -274,7 +219,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 { @@ -352,7 +297,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface break; default: - throw new Exception('not supported'); + throw new \Exception('not supported'); } try { @@ -365,7 +310,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; @@ -407,7 +352,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; @@ -442,7 +387,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $event = $this->machineHttpClient->request( 'POST', - 'users/' . $userId . '/calendar/events', + 'users/'.$userId.'/calendar/events', [ 'json' => $eventData, ] @@ -486,7 +431,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface ] = $this->createOnRemote( $eventData, $calendarRange->getUser(), - 'calendar_range_' . $calendarRange->getId() + 'calendar_range_'.$calendarRange->getId() ); $calendarRange->setRemoteId($id) @@ -509,7 +454,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 { @@ -527,8 +472,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']; @@ -539,19 +484,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', [ @@ -562,11 +501,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]; } @@ -581,17 +520,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) { @@ -604,9 +543,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface } $this->cacheScheduleTimeForUser[$userId] = array_map( - function ($item) { - return $this->remoteEventConverter->convertAvailabilityToRemoteEvent($item); - }, + fn ($item) => $this->remoteEventConverter->convertAvailabilityToRemoteEvent($item), $response['value'][0]['scheduleItems'] ); @@ -618,7 +555,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); } @@ -632,7 +569,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $calendar->getRemoteId(), $eventData, $calendar->getMainUser(), - 'calendar_' . $calendar->getId() + 'calendar_'.$calendar->getId() ); $calendar->addRemoteAttributes([ @@ -663,7 +600,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface try { $event = $this->machineHttpClient->request( 'PATCH', - 'users/' . $userId . '/calendar/events/' . $remoteId, + 'users/'.$userId.'/calendar/events/'.$remoteId, [ 'json' => $eventData, ] @@ -691,9 +628,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(), @@ -718,7 +655,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', [ @@ -745,7 +682,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..b4e2a5d3a 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,28 +41,18 @@ 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 []; } - public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, ?CalendarRange $associatedCalendarRange = null): void - { - } + public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, CalendarRange $associatedCalendarRange = null): void {} - public function removeCalendarRange(string $remoteId, array $remoteAttributes, User $user): void - { - } + public function removeCalendarRange(string $remoteId, array $remoteAttributes, User $user): void {} - public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void - { - } + public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void {} - public function syncCalendarRange(CalendarRange $calendarRange): void - { - } + public function syncCalendarRange(CalendarRange $calendarRange): void {} - public function syncInvite(Invite $invite): void - { - } + public function syncInvite(Invite $invite): void {} } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php index e3ef89ca4..53d2c8602 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,9 +45,9 @@ 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; + public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, CalendarRange $associatedCalendarRange = null): void; public function removeCalendarRange(string $remoteId, array $remoteAttributes, User $user): void; diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php index f56735de7..c52fc2866 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php @@ -23,10 +23,11 @@ use Chill\CalendarBundle\Command\MapAndSubscribeUserCalendarCommand; use Chill\CalendarBundle\Controller\RemoteCalendarConnectAzureController; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineHttpClient; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineTokenStorage; +use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReaderInterface; +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; @@ -37,17 +38,13 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface public function process(ContainerBuilder $container) { $config = $container->getParameter('chill_calendar'); - $connector = null; - if (!$config['remote_calendars_sync']['enabled']) { - $connector = NullRemoteCalendarConnector::class; - } - - if ($config['remote_calendars_sync']['microsoft_graph']['enabled']) { + 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 $container->removeDefinition(MapAndSubscribeUserCalendarCommand::class); $container->removeDefinition(AzureGrantAdminConsentAndAcquireToken::class); @@ -55,16 +52,14 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface $container->removeDefinition(MachineTokenStorage::class); $container->removeDefinition(MachineHttpClient::class); $container->removeDefinition(MSGraphRemoteCalendarConnector::class); + $container->removeDefinition(MSUserAbsenceReaderInterface::class); + $container->removeDefinition(MSUserAbsenceSync::class); } if (!$container->hasAlias(Azure::class) && $container->hasDefinition('knpu.oauth2.client.azure')) { $container->setAlias(Azure::class, 'knpu.oauth2.provider.azure'); } - if (null === $connector) { - throw new RuntimeException('Could not configure remote calendar'); - } - foreach ([ NullRemoteCalendarConnector::class, MSGraphRemoteCalendarConnector::class, ] as $serviceId) { diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php index 13c4a1c6a..40d1d1435 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php @@ -18,45 +18,31 @@ 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..8f490f376 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php @@ -23,25 +23,14 @@ 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; + public function __construct(private readonly AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository, private readonly EntityManagerInterface $em) {} - private EntityManagerInterface $em; - - public function __construct( - AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository, - EntityManagerInterface $em - ) { - $this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository; - $this->em = $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 +53,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 +79,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 +103,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 +124,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 +139,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 +157,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 +176,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..f6d1b5c17 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..fb71717d0 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) { @@ -35,7 +35,7 @@ class CalendarDocRepository implements ObjectRepository, CalendarDocRepositoryIn return $this->repository->findAll(); } - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null) { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepositoryInterface.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepositoryInterface.php index d2b1951df..6d86fd943 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepositoryInterface.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepositoryInterface.php @@ -25,7 +25,7 @@ interface CalendarDocRepositoryInterface /** * @return array|CalendarDoc[] */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null); + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null); public function findOneBy(array $criteria): ?CalendarDoc; diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php index dc3405b7e..a707ccde9 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)') @@ -57,7 +52,7 @@ class CalendarRangeRepository implements ObjectRepository /** * @return array|CalendarRange[] */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null) { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } @@ -67,10 +62,10 @@ class CalendarRangeRepository implements ObjectRepository */ public function findByAvailableRangesForUser( User $user, - DateTimeImmutable $from, - DateTimeImmutable $to, - ?int $limit = null, - ?int $offset = null + \DateTimeImmutable $from, + \DateTimeImmutable $to, + int $limit = null, + int $offset = null ): array { $qb = $this->buildQueryAvailableRangesForUser($user, $from, $to); @@ -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..486493b21 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)') @@ -48,7 +46,7 @@ class CalendarRepository implements ObjectRepository ->getSingleScalarResult(); } - public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder + public function createQueryBuilder(string $alias, string $indexBy = null): QueryBuilder { return $this->repository->createQueryBuilder($alias, $indexBy); } @@ -69,7 +67,7 @@ class CalendarRepository implements ObjectRepository /** * @return array|Calendar[] */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } @@ -77,7 +75,7 @@ class CalendarRepository implements ObjectRepository /** * @return array|Calendar[] */ - public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $orderBy = null, int $limit = null, int $offset = null): array { return $this->findBy( [ @@ -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..c6f163aa3 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) { @@ -41,7 +41,7 @@ class InviteRepository implements ObjectRepository /** * @return array|Invite[] */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null) { return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset); } 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/public/chill/chill.js b/src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js new file mode 100644 index 000000000..56a8ce563 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js @@ -0,0 +1 @@ +import './scss/badge.scss'; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss new file mode 100644 index 000000000..ffcda8f0f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss @@ -0,0 +1,25 @@ +@import '~ChillPersonAssets/chill/scss/mixins.scss'; +@import '~ChillMainAssets/module/bootstrap/shared'; + +.badge-calendar { + display: inline-block; + background-color: #f3f3f3; + + .title_label { + @include chill_badge($chill-l-gray); + } + + .title_action { + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + margin-right: 1rem; + + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + } +} + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss index a2c0c4b89..ce54b0fa8 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss @@ -17,4 +17,4 @@ span.calendarRangeItems { text-decoration: none; padding: 3px; } -} \ No newline at end of file +} 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/Resources/views/GenericDoc/calendar_document.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig new file mode 100644 index 000000000..facf5be50 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig @@ -0,0 +1,75 @@ +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} +{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} +{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} + +{% set c = document.calendar %} + +
      +
      +
      + {% if document.storedObject.isPending %} +
      {{ 'docgen.Doc generation is pending'|trans }}
      + {% elseif document.storedObject.isFailure %} +
      {{ 'docgen.Doc generation failed'|trans }}
      + {% endif %} + +
      + {% if c.accompanyingPeriod is not null and context == 'person' %} + + {{ c.accompanyingPeriod.id }} +   + {% endif %} + + + + + {{ 'Calendar'|trans }} + {% if c.endDate.diff(c.startDate).days >= 1 %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('short', 'short') }} + {% else %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('none', 'short') }} + {% endif %} + + +
      + +
      + {{ document.storedObject.title|chill_print_or_message("No title") }} +
      + {% if document.storedObject.hasTemplate %} +
      +

      {{ document.storedObject.template.name|localize_translatable_string }}

      +
      + {% endif %} +
      + +
      +
      +
      + {{ document.storedObject.createdAt|format_date('short') }} +
      +
      +
      +
      + +
      +
      + {{ mmm.createdBy(document) }} +
      +
        + {% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %} +
      • + {{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }} +
      • + {% endif %} + {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %} +
      • + +
      • + {% endif %} +
      + +
      +
      diff --git a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php index 452286e85..a0e653cb1 100644 --- a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php @@ -15,47 +15,34 @@ 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) - { - $this->security = $security; - } + public function __construct(private readonly 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..087f5ea86 100644 --- a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php +++ b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php @@ -29,45 +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 - ) { - $this->baseContextData = $baseContextData; - $this->entityManager = $entityManager; - $this->normalizer = $normalizer; - $this->personRender = $personRender; - $this->personRepository = $personRepository; - $this->thirdPartyRender = $thirdPartyRender; - $this->thirdPartyRepository = $thirdPartyRepository; - $this->translatableStringHelper = $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 + ) {} public function adminFormReverseTransform(array $data): array { @@ -147,8 +121,6 @@ final class CalendarContext implements CalendarContextInterface } } - /** - */ public function getData(DocGeneratorTemplate $template, mixed $entity, array $contextGenerationData = []): array { $options = $this->getOptions($template); @@ -198,7 +170,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 +178,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..09a333f3f 100644 --- a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php +++ b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php @@ -12,17 +12,11 @@ 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 * @extends DocGeneratorContextWithAdminFormInterface */ -interface CalendarContextInterface extends DocGeneratorContextWithPublicFormInterface, DocGeneratorContextWithAdminFormInterface -{ -} +interface CalendarContextInterface extends DocGeneratorContextWithPublicFormInterface, DocGeneratorContextWithAdminFormInterface {} diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php new file mode 100644 index 000000000..5e4e7de83 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -0,0 +1,191 @@ +em->getClassMetadata(CalendarDoc::class); + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $calendarMetadata = $this->em->getClassMetadata(Calendar::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')), + 'cd.'.$storedObjectMetadata->getColumnName('createdAt'), + $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' + ); + $query->addJoinClause( + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + ) + ); + + $query->addJoinClause( + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', + $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), + $calendarMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('calendar') + ) + ); + + $query->addWhereClause( + sprintf( + 'calendar.%s = ?', + $calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name'] + ), + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + 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 + { + $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $calendarMetadata = $this->em->getClassMetadata(Calendar::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')), + 'cd.'.$storedObjectMetadata->getColumnName('createdAt'), + $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' + ); + $query->addJoinClause( + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + ) + ); + + $query->addJoinClause( + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', + $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), + $calendarMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('calendar') + ) + ); + + // get the documents associated with accompanying periods in which person participates + $or = []; + $orParams = []; + $orTypes = []; + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(CalendarVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $or[] = sprintf( + '(calendar.%s = ? AND cd.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', + $calendarMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), + $storedObjectMetadata->getColumnName('createdAt') + ); + $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), + \DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : \DateTimeImmutable::createFromInterface($participation->getEndDate())]; + $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; + } + + if ([] === $or) { + $query->addWhereClause('TRUE = FALSE'); + + return $query; + } + + $query->addWhereClause(implode(' OR ', $or), $orParams, $orTypes); + + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); + } + + public function isAllowedForPerson(Person $person): bool + { + // check that the person is allowed to see an accompanying period. If yes, the + // ACL on each accompanying period will be checked when the query is build + return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); + } + + private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), + ['%'.$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 new file mode 100644 index 000000000..49e890ca0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php @@ -0,0 +1,119 @@ +em->getClassMetadata(StoredObject::class); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), + ['%'.$content.'%'], + [Types::STRING] + ); + } + + return $query; + } + + /** + * @throws MappingException + */ + 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); + $calendarMetadata = $this->em->getClassMetadata(Calendar::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')), + 'cd.'.$storedObjectMetadata->getColumnName('createdAt'), + $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' + ); + $query->addJoinClause( + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + ) + ); + + $query->addJoinClause( + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', + $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), + $calendarMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('calendar') + ) + ); + + $query->addWhereClause( + sprintf('calendar.%s = ?', $calendarMetadata->getSingleAssociationJoinColumnName('person')), + [$person->getId()], + [Types::INTEGER] + ); + + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); + } + + 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 new file mode 100644 index 000000000..123afc164 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php @@ -0,0 +1,41 @@ +key || PersonCalendarGenericDocProvider::KEY === $genericDocDTO->key; + } + + public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string + { + return '@ChillCalendar/GenericDoc/calendar_document.html.twig'; + } + + public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array + { + return [ + 'document' => $this->repository->find($genericDocDTO->identifiers['id']), + 'context' => $genericDocDTO->getContext(), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php index a35fffce0..9a4a92a94 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php @@ -19,38 +19,20 @@ 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) - { - $this->provider = $provider; - $this->em = $em; - $this->logger = $logger; - $this->messageBus = $messageBus; - $this->messageForCalendarBuilder = $messageForCalendarBuilder; - } + public function __construct(private readonly CalendarForShortMessageProvider $provider, private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly MessageBusInterface $messageBus, private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder) {} public function sendBulkMessageToEligibleCalendars() { $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 +41,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(__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..31b870ed4 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php @@ -20,27 +20,11 @@ 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) {} /** * Generate calendars instance. @@ -49,7 +33,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 +60,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..880c9950f 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php @@ -20,17 +20,10 @@ 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 80b2f8c45..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,24 +111,18 @@ 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()); $this->assertEquals(true, $calendar->getSendSMS()); - $this->assertContains(2, $calendar->getUsers()->map(static function (User $u) { - return $u->getId(); - })); - $this->assertContains(3, $calendar->getUsers()->map(static function (User $u) { - return $u->getId(); - })); + $this->assertContains(2, $calendar->getUsers()->map(static fn (User $u) => $u->getId())); + $this->assertContains(3, $calendar->getUsers()->map(static fn (User $u) => $u->getId())); } protected function getExtensions() { - $parents = parent::getExtensions(); - $calendarType = new CalendarType( $this->personsToIdDataTransformer, $this->idToUserDataTransformer, @@ -136,37 +133,44 @@ 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 ) { $transformer = $this->prophesize($classTransformer); $transformer->transform(Argument::type('array')) - ->will(static function ($args) { - return implode( - ',', - array_map(static function ($p) { - return $p->getId(); - }, $args[0]) - ); - }); + ->will(static fn ($args) => implode( + ',', + array_map(static fn ($p) => $p->getId(), $args[0]) + )); $transformer->transform(Argument::exact(null)) ->willReturn([]); $transformer->transform(Argument::type(Collection::class)) - ->will(static function ($args) { - return implode( - ',', - array_map(static function ($p) { - return $p->getId(); - }, $args[0]->toArray()) - ); - }); + ->will(static fn ($args) => implode( + ',', + array_map(static fn ($p) => $p->getId(), $args[0]->toArray()) + )); $transformer->reverseTransform(Argument::type('string')) ->will(static function ($args) use ($objClass) { if (null === $args[0]) { @@ -176,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]) ); }); @@ -195,9 +199,7 @@ final class CalendarTypeTest extends TypeTestCase ) { $transformer = $this->prophesize($classTransformer); $transformer->transform(Argument::type('object')) - ->will(static function ($args) { - return (string) $args[0]->getId(); - }); + ->will(static fn ($args) => (string) $args[0]->getId()); $transformer->transform(Argument::exact(null)) ->willReturn(''); $transformer->reverseTransform(Argument::type('string')) @@ -206,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 485146939..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,11 +61,9 @@ 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 function ($args): string { - return ($args[0] ?? ['fr' => 'not provided'])['fr'] ?? 'not provided'; - }); + $translatableStringHelper->localize(Argument::type('array'))->will(static fn ($args): string => ($args[0] ?? ['fr' => 'not provided'])['fr'] ?? 'not provided'); $addressRender = new AddressRender($engine->reveal(), $translatableStringHelper->reveal()); 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..8f6cb9335 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 @@ -362,9 +361,9 @@ final class CalendarSyncerTest extends TestCase parent::setUp(); // all tests should run when timezone = +02:00 - $brussels = new DateTimeZone('Europe/Brussels'); + $brussels = new \DateTimeZone('Europe/Brussels'); - if (7200 === $brussels->getOffset(new DateTimeImmutable())) { + if (7200 === $brussels->getOffset(new \DateTimeImmutable())) { date_default_timezone_set('Europe/Brussels'); } else { date_default_timezone_set('Europe/Moscow'); @@ -403,8 +402,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 +479,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 +497,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 +523,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 +540,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 +567,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 +584,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 new file mode 100644 index 000000000..4307f32a3 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php @@ -0,0 +1,176 @@ +prophesize(MapCalendarToUser::class); + $mapUser->getUserId($user)->willReturn('1234'); + $clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00')); + + $absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock); + + self::assertEquals($expected, $absenceReader->isUserAbsent($user), $message); + } + + public function testIsUserAbsentWithoutRemoteId(): void + { + $user = new User(); + $client = new MockHttpClient(); + + $mapUser = $this->prophesize(MapCalendarToUser::class); + $mapUser->getUserId($user)->willReturn(null); + $clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00')); + + $absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock); + + self::assertNull($absenceReader->isUserAbsent($user), 'when no user found, absence should be null'); + } + + public function provideDataTestUserAbsence(): iterable + { + // contains data that was retrieved from microsoft graph api on 2023-07-06 + + yield [ + <<<'JSON' + { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting", + "status": "disabled", + "externalAudience": "none", + "internalReplyMessage": "Je suis en congé.", + "externalReplyMessage": "", + "scheduledStartDateTime": { + "dateTime": "2023-07-06T12:00:00.0000000", + "timeZone": "UTC" + }, + "scheduledEndDateTime": { + "dateTime": "2023-07-07T12:00:00.0000000", + "timeZone": "UTC" + } + } + JSON, + false, + 'User is present', + ]; + + yield [ + <<<'JSON' + { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting", + "status": "scheduled", + "externalAudience": "none", + "internalReplyMessage": "Je suis en congé.", + "externalReplyMessage": "", + "scheduledStartDateTime": { + "dateTime": "2023-07-06T11:00:00.0000000", + "timeZone": "UTC" + }, + "scheduledEndDateTime": { + "dateTime": "2023-07-21T11:00:00.0000000", + "timeZone": "UTC" + } + } + JSON, + true, + 'User is absent with absence scheduled, we are within this period', + ]; + + yield [ + <<<'JSON' + { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting", + "status": "scheduled", + "externalAudience": "none", + "internalReplyMessage": "Je suis en congé.", + "externalReplyMessage": "", + "scheduledStartDateTime": { + "dateTime": "2023-07-08T11:00:00.0000000", + "timeZone": "UTC" + }, + "scheduledEndDateTime": { + "dateTime": "2023-07-21T11:00:00.0000000", + "timeZone": "UTC" + } + } + JSON, + false, + 'User is present: absence is scheduled for later', + ]; + + yield [ + <<<'JSON' + { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting", + "status": "scheduled", + "externalAudience": "none", + "internalReplyMessage": "Je suis en congé.", + "externalReplyMessage": "", + "scheduledStartDateTime": { + "dateTime": "2023-07-05T11:00:00.0000000", + "timeZone": "UTC" + }, + "scheduledEndDateTime": { + "dateTime": "2023-07-06T11:00:00.0000000", + "timeZone": "UTC" + } + } + JSON, + false, + 'User is present: absence is past', + ]; + + yield [ + <<<'JSON' + { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting", + "status": "alwaysEnabled", + "externalAudience": "none", + "internalReplyMessage": "Je suis en congé.", + "externalReplyMessage": "", + "scheduledStartDateTime": { + "dateTime": "2023-07-06T12:00:00.0000000", + "timeZone": "UTC" + }, + "scheduledEndDateTime": { + "dateTime": "2023-07-07T12:00:00.0000000", + "timeZone": "UTC" + } + } + JSON, + true, + '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 new file mode 100644 index 000000000..244c7e468 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSyncTest.php @@ -0,0 +1,68 @@ +prophesize(MSUserAbsenceReaderInterface::class); + $userAbsenceReader->isUserAbsent($user)->willReturn($absenceFromMicrosoft); + + $clock = new MockClock(new \DateTimeImmutable('2023-07-01T12:00:00')); + + $syncer = new MSUserAbsenceSync($userAbsenceReader->reveal(), $clock, new NullLogger()); + + $syncer->syncUserAbsence($user); + + self::assertEquals($expectedAbsence, $user->isAbsent(), $message); + self::assertEquals($expectedAbsenceStart, $user->getAbsenceStart(), $message); + } + + 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']; + + $user = new User(); + $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']; + + 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']; + } +} 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..506baa98b 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'); } @@ -201,8 +202,8 @@ final class CalendarContextTest extends TestCase } private function buildCalendarContext( - ?EntityManagerInterface $entityManager = null, - ?NormalizerInterface $normalizer = null + EntityManagerInterface $entityManager = null, + NormalizerInterface $normalizer = null ): CalendarContext { $baseContext = $this->prophesize(BaseContextData::class); $baseContext->getData(null)->willReturn(['base_context' => 'data']); @@ -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 f3e35ef93..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,30 +58,26 @@ 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 function ($args) { - return array_fill(0, $args[2], new Calendar()); - })->shouldBeCalledTimes(1); + )->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 function ($args) { - return array_fill(0, $args[2] - 1, new Calendar()); - })->shouldBeCalledTimes(1); + )->will(static fn ($args) => array_fill(0, $args[2] - 1, new Calendar()))->shouldBeCalledTimes(1); $em = $this->prophesize(EntityManagerInterface::class); $em->clear()->shouldBeCalled(); @@ -93,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); } @@ -104,21 +99,17 @@ 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 function ($args) { - return array_fill(0, 1, new Calendar()); - })->shouldBeCalledTimes(1); + )->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 function ($args) { - return []; - })->shouldBeCalledTimes(1); + )->will(static fn ($args) => [])->shouldBeCalledTimes(1); $em = $this->prophesize(EntityManagerInterface::class); $em->clear()->shouldBeCalled(); @@ -129,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/chill.webpack.config.js b/src/Bundle/ChillCalendarBundle/chill.webpack.config.js index e82210087..9d45a3142 100644 --- a/src/Bundle/ChillCalendarBundle/chill.webpack.config.js +++ b/src/Bundle/ChillCalendarBundle/chill.webpack.config.js @@ -1,6 +1,8 @@ // this file loads all assets from the Chill calendar bundle module.exports = function(encore, entries) { + entries.push(__dirname + '/Resources/public/chill/chill.js'); + encore.addAliases({ ChillCalendarAssets: __dirname + '/Resources/public' }); diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index eb02be280..d157683e0 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -43,6 +43,7 @@ crud: title_edit: Modifier le motif d'annulation chill_calendar: + Document: Document d'un rendez-vous form: The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement. Create for referrer: Créer pour le référent @@ -65,6 +66,7 @@ chill_calendar: Document outdated: La date et l'heure du rendez-vous ont été modifiés après la création du document + remote_ms_graph: freebusy_statuses: busy: Occupé @@ -97,24 +99,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 @@ -145,3 +155,9 @@ CHILL_CALENDAR_CALENDAR_EDIT: Modifier les rendez-vous CHILL_CALENDAR_CALENDAR_DELETE: Supprimer les rendez-vous CHILL_CALENDAR_CALENDAR_SEE: Voir les rendez-vous + +generic_doc: + filter: + keys: + accompanying_period_calendar_document: Document des rendez-vous des parcours + person_calendar_document: Document des rendez-vous de l'usager 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 c8c8ad4fc..08425bcf0 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,10 +79,7 @@ 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'); @@ -122,9 +89,9 @@ class CreateFieldsOnGroupCommand extends Command ->getRepository(\Chill\CustomFieldsBundle\Entity\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,23 +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 - $names[] = (isset($cf->getName()[$lang])) ? - $cf->getName()[$lang] : - 'Not available in this language'; + // 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); } } @@ -215,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; @@ -231,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) { @@ -241,17 +207,15 @@ 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 - $row[] = (isset($customFieldGroup->getName()[$lang])) ? - $customFieldGroup->getName()[$lang] : - 'Not available in this language'; + // 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..3bba08b01 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php +++ b/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php @@ -17,6 +17,7 @@ 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; /** * Class CustomFieldController. @@ -25,6 +26,8 @@ class CustomFieldController extends AbstractController { /** * Creates a new CustomField entity. + * + * @Route("/{_locale}/admin/customfield/new", name="customfield_new") */ public function createAction(Request $request) { @@ -32,7 +35,7 @@ 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(); @@ -40,16 +43,13 @@ class CustomFieldController extends AbstractController $this->addFlash('success', $this->get('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') ->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 +58,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 +72,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 +80,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,47 +96,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. - * - * @deprecated is not used since there is no link to show action - * - * @param mixed $id - */ - public function showAction($id) - { - $em = $this->getDoctrine()->getManager(); - - $entity = $em->getRepository(CustomField::class)->find($id); - - if (!$entity) { - 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 + * @Route("/{_locale}/admin/customfield/update", name="customfield_update") */ - public function updateAction(Request $request, $id) + public function updateAction(Request $request, mixed $id) { $em = $this->getDoctrine()->getManager(); @@ -147,19 +127,19 @@ class CustomFieldController extends AbstractController $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') ->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') ->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 +148,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 +171,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..829ca5c55 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php +++ b/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php @@ -25,37 +25,23 @@ 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 +49,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 +57,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,9 +72,9 @@ 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(); @@ -100,7 +86,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,6 +94,8 @@ class CustomFieldsGroupController extends AbstractController /** * Lists all CustomFieldsGroup entities. + * + * @Route("/{_locale}/admin/customfieldsgroup/", name="customfieldsgroup") */ public function indexAction() { @@ -119,12 +107,12 @@ class CustomFieldsGroupController extends AbstractController $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 +121,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) { @@ -146,9 +136,7 @@ class CustomFieldsGroupController extends AbstractController $cFGroup = $em->getRepository(\Chill\CustomFieldsBundle\Entity\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) @@ -173,18 +161,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(), ]); @@ -217,20 +207,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,9 +232,9 @@ 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(); @@ -256,7 +246,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,9 +256,9 @@ 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(); @@ -281,19 +271,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 +293,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,11 +359,9 @@ 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) + private function createMakeDefaultForm(CustomFieldsGroup $group = null) { return $this->createFormBuilder($group, [ 'method' => 'POST', @@ -396,8 +383,8 @@ class CustomFieldsGroupController extends AbstractController $em = $this->getDoctrine()->getManager(); $customFieldsGroupIds = $em->createQuery('SELECT g.id FROM ' - . 'ChillCustomFieldsBundle:CustomFieldsDefaultGroup d ' - . 'JOIN d.customFieldsGroup g') + .'ChillCustomFieldsBundle:CustomFieldsDefaultGroup d ' + .'JOIN d.customFieldsGroup g') ->getResult(Query::HYDRATE_SCALAR); $result = []; @@ -418,7 +405,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 7876dd026..8fd91f27d 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php @@ -18,54 +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 - ) { - $this->defaultLocales = $translator->getFallbackLocales(); - $this->templating = $templating; - $this->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 + ) {} public function allowOtherChoice(CustomField $cf) { @@ -74,7 +52,7 @@ class CustomFieldChoice extends AbstractCustomField public function buildForm(FormBuilderInterface $builder, CustomField $customField) { - //prepare choices + // prepare choices $choices = []; $customFieldOptions = $customField->getOptions(); @@ -84,7 +62,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 +70,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 +90,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( @@ -177,7 +155,7 @@ class CustomFieldChoice extends AbstractCustomField return $serialized; } - public function extractOtherValue(CustomField $cf, ?array $data = null) + public function extractOtherValue(CustomField $cf, array $data = null) { return $data['_other']; } @@ -198,7 +176,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 +201,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 +214,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 +234,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 +244,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 +253,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,23 +264,20 @@ 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 - $data = (isset($value['_choices'])) ? $value['_choices'] : $value; - $selected = (is_array($data)) ? $data : [$data]; + // 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]; $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'; @@ -316,7 +291,6 @@ class CustomFieldChoice extends AbstractCustomField 'selected' => $selected, 'multiple' => $customField->getOptions()[self::MULTIPLE], 'expanded' => $customField->getOptions()[self::EXPANDED], - 'locales' => $this->defaultLocales, ] ); } @@ -330,15 +304,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 +325,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 +353,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(null|array|string $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..a535f805e 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,32 +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 +68,8 @@ class CustomFieldDate extends AbstractCustomField { $validatorFunction = static function ($value, ExecutionContextInterface $context) { try { - $date = new DateTime($value); - } catch (Exception $e) { + $date = new \DateTime($value); + } catch (\Exception) { $context->buildViolation('The expression "%expression%" is invalid', [ '%expression%' => $value, ]) @@ -117,7 +104,7 @@ class CustomFieldDate extends AbstractCustomField return null; } - return DateTime::createFromFormat(self::DATE_FORMAT, $serialized); + return \DateTime::createFromFormat(self::DATE_FORMAT, $serialized); } public function getName() @@ -139,7 +126,7 @@ class CustomFieldDate extends AbstractCustomField default: $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:date.' - . $documentType . '.twig'; + .$documentType.'.twig'; return $this->templating ->render($template, [ @@ -175,7 +162,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 +173,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 +194,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..ff89c04d2 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); @@ -58,9 +57,8 @@ interface CustomFieldInterface /** * Return a repsentation of the value of the CustomField. * - * @param mixed $value the raw value, **not deserialized** (= as stored in the db) + * @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 */ @@ -69,7 +67,6 @@ interface CustomFieldInterface /** * 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 41c8b4694..885a02f2d 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php @@ -17,34 +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 - ) { - $this->optionRepository = $optionRepository; - $this->translatableStringHelper = $translatableStringHelper; - $this->templating = $twigEngine; - } + private readonly OptionRepository $optionRepository, + private readonly TranslatableStringHelper $translatableStringHelper, + private readonly \Twig\Environment $templating, + ) {} public function buildForm(FormBuilderInterface $builder, CustomField $customField) { @@ -54,13 +38,11 @@ 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, - 'choice_label' => static function (Option $option) use ($translatableStringHelper) { - return $translatableStringHelper->localize($option->getText()); - }, + 'choice_label' => static fn (Option $option) => $translatableStringHelper->localize($option->getText()), 'choice_value' => static fn (Option $key): ?int => null === $key ? null : $key->getId(), 'multiple' => false, 'expanded' => false, @@ -83,7 +65,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 = []; @@ -115,7 +97,7 @@ class CustomFieldLongChoice extends AbstractCustomField { $option = $this->deserialize($value, $customField); $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice_long.' - . $documentType . '.twig'; + .$documentType.'.twig'; return $this->templating ->render($template, [ @@ -130,9 +112,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..979d96540 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,30 @@ 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); @@ -107,7 +96,7 @@ class CustomFieldNumber extends AbstractCustomField public function render($value, CustomField $customField, $documentType = 'html') { $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:number.' - . $documentType . '.twig'; + .$documentType.'.twig'; $options = $customField->getOptions(); return $this->templating @@ -142,7 +131,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..f95048e21 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php @@ -13,43 +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 - ) { - $this->requestStack = $requestStack; - $this->templating = $templating; - $this->translatableStringHelper = $translatableStringHelper; - } + private readonly Environment $templating, + private readonly TranslatableStringHelper $translatableStringHelper + ) {} /** * Create a form according to the maxLength option. @@ -67,7 +47,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,7 +86,7 @@ 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'; diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php index b22b84029..302e04350 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php @@ -14,40 +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 - ) { - $this->requestStack = $requestStack; - $this->templating = $templating; - $this->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 + ) {} public function buildForm(FormBuilderInterface $builder, CustomField $customField) { @@ -95,7 +80,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 0edc2457a..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,8 +103,8 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface ->setKey('company'); $manager->persist($parent); - //Load children - $expected_nb_children = mt_rand(10, 50); + // Load children + $expected_nb_children = random_int(10, 50); for ($i = 0; $i < $expected_nb_children; ++$i) { $companyName = $this->fakerFr->company; @@ -143,8 +143,8 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface ->setKey('word'); $manager->persist($parent); - //Load children - $expected_nb_children = mt_rand(10, 50); + // Load children + $expected_nb_children = random_int(10, 50); for ($i = 0; $i < $expected_nb_children; ++$i) { $manager->persist($this->createChildOption($parent, [ 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/Configuration.php b/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/Configuration.php index d0db269a2..e68fdd2c4 100644 --- a/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/Configuration.php @@ -25,7 +25,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder('chill_custom_fields'); - $rootNode = $treeBuilder->getRootNode('chill_custom_fields'); + $rootNode = $treeBuilder->getRootNode(); $classInfo = 'The class which may receive custom fields'; $nameInfo = 'The name which will appears in the user interface. May be translatable'; 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..b75d23e5e 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 ?\Chill\CustomFieldsBundle\Entity\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,11 +210,9 @@ class CustomField /** * Set customFieldGroup. * - * @param CustomFieldsGroup $customFieldGroup - * * @return CustomField */ - public function setCustomFieldsGroup(?CustomFieldsGroup $customFieldGroup = null) + public function setCustomFieldsGroup(CustomFieldsGroup $customFieldGroup = null) { $this->customFieldGroup = $customFieldGroup; @@ -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..b6f371c8e 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